From 1c33d91f1a54a0174192db49b737eb463cdb032c Mon Sep 17 00:00:00 2001 From: Hoky <303455184@qq.com> Date: Tue, 9 Nov 2021 16:51:57 +0800 Subject: [PATCH] =?UTF-8?q?REPORT-60163=20=E5=85=AC=E5=BC=8F=E7=BC=96?= =?UTF-8?q?=E8=BE=91=E5=99=A8=E4=BC=98=E5=8C=962.0=201.=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E4=BA=86=E8=BE=93=E5=85=A5=E6=8F=90=E7=A4=BA=EF=BC=9B=202.?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BA=86=E6=90=9C=E7=B4=A2=E6=A1=86=EF=BC=9B?= =?UTF-8?q?=203.=E4=BC=98=E5=8C=96=E4=BA=86=E5=87=BA=E9=94=99=E6=8F=90?= =?UTF-8?q?=E7=A4=BA=E4=BB=A5=E5=8F=8A=E6=B7=BB=E5=8A=A0=E4=BA=86=E5=85=B3?= =?UTF-8?q?=E9=97=AD=E5=89=8D=E6=A3=80=E6=B5=8B=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/fr/design/dialog/BasicPane.java | 17 +- .../java/com/fr/design/dialog/UIDialog.java | 26 +- .../com/fr/design/formula/FormulaChecker.java | 78 +- .../com/fr/design/formula/FormulaPane.java | 341 +++-- .../FormulaPaneWhenReserveFormula.java | 9 +- .../FormulaExceptionTipsProcessor.java | 48 + .../function/FormulaCheckConstants.java | 31 + .../function/FormulaCheckWrongFunction.java | 74 + .../function/MismatchedCharFunction.java | 103 ++ .../function/MismatchedTokenFunction.java | 139 ++ .../function/NoViableAltForCharFunction.java | 43 + .../function/NoViableAltFunction.java | 40 + .../FormulaAutoCompletePopupWindow.java | 1003 +++++++++++++ .../gui/autocomplete/FormulaCompletion.java | 22 + .../FormulaPaneAutoCompletion.java | 1311 +++++++++++++++++ .../ParameterizedCompletionContext.java | 28 +- .../com/fr/design/images/m_file/formula.png | Bin 0 -> 279 bytes .../com/fr/design/images/m_file/param.png | Bin 0 -> 383 bytes 18 files changed, 3143 insertions(+), 170 deletions(-) create mode 100644 designer-base/src/main/java/com/fr/design/formula/exception/FormulaExceptionTipsProcessor.java create mode 100644 designer-base/src/main/java/com/fr/design/formula/exception/function/FormulaCheckConstants.java create mode 100644 designer-base/src/main/java/com/fr/design/formula/exception/function/FormulaCheckWrongFunction.java create mode 100644 designer-base/src/main/java/com/fr/design/formula/exception/function/MismatchedCharFunction.java create mode 100644 designer-base/src/main/java/com/fr/design/formula/exception/function/MismatchedTokenFunction.java create mode 100644 designer-base/src/main/java/com/fr/design/formula/exception/function/NoViableAltForCharFunction.java create mode 100644 designer-base/src/main/java/com/fr/design/formula/exception/function/NoViableAltFunction.java create mode 100644 designer-base/src/main/java/com/fr/design/gui/autocomplete/FormulaAutoCompletePopupWindow.java create mode 100644 designer-base/src/main/java/com/fr/design/gui/autocomplete/FormulaCompletion.java create mode 100644 designer-base/src/main/java/com/fr/design/gui/autocomplete/FormulaPaneAutoCompletion.java create mode 100644 designer-base/src/main/resources/com/fr/design/images/m_file/formula.png create mode 100644 designer-base/src/main/resources/com/fr/design/images/m_file/param.png diff --git a/designer-base/src/main/java/com/fr/design/dialog/BasicPane.java b/designer-base/src/main/java/com/fr/design/dialog/BasicPane.java index d3c592a4e..75626441b 100644 --- a/designer-base/src/main/java/com/fr/design/dialog/BasicPane.java +++ b/designer-base/src/main/java/com/fr/design/dialog/BasicPane.java @@ -8,9 +8,14 @@ import com.fr.design.i18n.Toolkit; import com.fr.design.utils.gui.GUICoreUtils; import com.fr.stable.core.PropertyChangeAdapter; -import javax.swing.*; +import javax.swing.JPanel; import javax.swing.event.DocumentEvent; -import java.awt.*; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dialog; +import java.awt.Dimension; +import java.awt.Frame; +import java.awt.Window; @Open public abstract class BasicPane extends JPanel { @@ -262,6 +267,10 @@ public abstract class BasicPane extends JPanel { public void checkValid() throws Exception { } + public boolean confirmContinueBeforeDoOK() { + return true; + } + public static class NamePane extends BasicPane { private UITextField nameTextField; private UILabel Name; @@ -390,6 +399,10 @@ public abstract class BasicPane extends JPanel { BasicPane.this.checkValid(); } + public boolean confirmContinueBeforeDoOK() { + return BasicPane.this.confirmContinueBeforeDoOK(); + } + } private class UnsizedDialog extends UIDialog { diff --git a/designer-base/src/main/java/com/fr/design/dialog/UIDialog.java b/designer-base/src/main/java/com/fr/design/dialog/UIDialog.java index c15812357..458daddab 100644 --- a/designer-base/src/main/java/com/fr/design/dialog/UIDialog.java +++ b/designer-base/src/main/java/com/fr/design/dialog/UIDialog.java @@ -33,6 +33,7 @@ public abstract class UIDialog extends JDialog { private BasicPane pane; private java.util.List listeners = new ArrayList(); private boolean isDoOKSucceed; + private boolean needExceptionCheck = true; public UIDialog(Frame parent) { @@ -151,6 +152,10 @@ public abstract class UIDialog extends JDialog { }); } + public void setNeedExceptionCheck(boolean needExceptionCheck) { + this.needExceptionCheck = needExceptionCheck; + } + /** * 添加监听器 @@ -172,14 +177,21 @@ public abstract class UIDialog extends JDialog { * 确定操作 */ public void doOK() { + //由于checkValid不可以加入自定义的弹窗以及操作,添加一个接口 + if (!confirmContinueBeforeDoOK()) { + return; + } + try { checkValid(); } catch (Exception exp) { - FineJOptionPane.showMessageDialog( - this, - exp.getMessage(), - com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Tool_Tips"), - JOptionPane.WARNING_MESSAGE); + if (needExceptionCheck) { + FineJOptionPane.showMessageDialog( + this, + exp.getMessage(), + com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Tool_Tips"), + JOptionPane.WARNING_MESSAGE); + } return; } @@ -253,6 +265,10 @@ public abstract class UIDialog extends JDialog { */ public abstract void checkValid() throws Exception; + public boolean confirmContinueBeforeDoOK() { + return true; + } + public void setButtonEnabled(boolean b) { this.okButton.setEnabled(b); } diff --git a/designer-base/src/main/java/com/fr/design/formula/FormulaChecker.java b/designer-base/src/main/java/com/fr/design/formula/FormulaChecker.java index 59446169d..1682ba307 100644 --- a/designer-base/src/main/java/com/fr/design/formula/FormulaChecker.java +++ b/designer-base/src/main/java/com/fr/design/formula/FormulaChecker.java @@ -1,91 +1,43 @@ package com.fr.design.formula; +import com.fr.design.formula.exception.FormulaExceptionTipsProcessor; import com.fr.design.i18n.Toolkit; -import com.fr.log.FineLoggerFactory; import com.fr.parser.FRLexer; import com.fr.parser.FRParser; import com.fr.script.checker.FunctionCheckerDispatcher; -import com.fr.script.checker.exception.ConditionCheckWrongException; -import com.fr.script.checker.exception.FunctionCheckWrongException; -import com.fr.script.rules.FunctionParameterType; -import com.fr.script.rules.FunctionRule; -import com.fr.stable.StringUtils; +import com.fr.script.checker.result.FormulaCheckResult; +import com.fr.script.checker.result.FormulaCoordinates; import com.fr.stable.script.Expression; import com.fr.stable.script.Node; +import com.fr.third.antlr.TokenStreamRecognitionException; import java.io.StringReader; -import java.util.List; /** * @author Hoky * @date 2021/9/28 */ public class FormulaChecker { - private static final String VALID_FORMULA = Toolkit.i18nText("Fine-Design_Basic_FormulaD_Valid_Formula"); - private static final String INVALID_FORMULA = Toolkit.i18nText("Fine-Design_Basic_FormulaD_Invalid_Formula"); - public static final String COLON = ":"; + public static final String VALID_FORMULA = Toolkit.i18nText("Fine-Design_Basic_FormulaD_Valid_Formula"); + public static final String INVALID_FORMULA = Toolkit.i18nText("Fine-Design_Basic_FormulaD_Invalid_Formula"); + private static FormulaExceptionTipsProcessor processor = FormulaExceptionTipsProcessor.getProcessor(); - public static String check(String formulaText) throws FormulaCheckerException { + public static FormulaCheckResult check(String formulaText) { + //过滤一些空格等符号 StringReader in = new StringReader(formulaText); - FRLexer lexer = new FRLexer(in); FRParser parser = new FRParser(lexer); try { Expression expression = parser.parse(); Node node = expression.getConditionalExpression(); - boolean valid = FunctionCheckerDispatcher.getInstance().getFunctionChecker(node).checkFunction(node); - if (valid) { - return VALID_FORMULA; - } else { - throw new FormulaCheckerException(INVALID_FORMULA); - } - } catch (ConditionCheckWrongException cce) { - String functionName = cce.getFunctionName(); - throw new FormulaCheckerException(functionName + Toolkit.i18nText("Fine-Design_Basic_Formula_Check_Condition_Tips") + COLON); - } catch (FunctionCheckWrongException ce) { - List rules = ce.getRules(); - String functionName = ce.getFunctionName(); - StringBuilder errorMsg = new StringBuilder(functionName + com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Formula_Check_Error_Tips") + COLON); - for (int i = 0; i < rules.size(); i++) { - errorMsg.append("("); - if (rules.get(i).getParameterList().isEmpty()) { - errorMsg.append(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Formula_No_Param")); - } - for (FunctionParameterType functionParameterType : rules.get(i).getParameterList()) { - errorMsg.append(getTypeString(functionParameterType)).append(","); - } - if (",".equals(errorMsg.charAt(errorMsg.length() - 1) + "")) { - errorMsg.deleteCharAt(errorMsg.length() - 1); - } - errorMsg.append(")"); - if (i != rules.size() - 1) { - errorMsg.append(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Formula_Check_Or")); - } - } - throw new FormulaCheckerException(errorMsg.toString()); + boolean valid = FunctionCheckerDispatcher.getInstance().getFunctionChecker(node).checkFunction(formulaText, node); + return new FormulaCheckResult(valid, valid ? VALID_FORMULA : INVALID_FORMULA, FormulaCoordinates.INVALID); } catch (Exception e) { - FineLoggerFactory.getLogger().error(e.getMessage(), e); - throw new FormulaCheckerException(INVALID_FORMULA); - // alex:继续往下面走,expression为null时告知不合法公式 - } - } - - private static String getTypeString(FunctionParameterType type) { - switch (type) { - case NUMBER: - return com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Formula_Check_ParamType_Number"); - case STRING: - return com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Formula_Check_ParamType_String"); - case ANY: - return com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Formula_Check_ParamType_Any"); - case DATE: - return com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Formula_Check_ParamType_Date"); - case BOOLEAN: - return com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Formula_Check_ParamType_Boolean"); - case ARRAY: - return com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Formula_Check_ParamType_Array"); + if (e instanceof TokenStreamRecognitionException) { + return processor.getExceptionTips(((TokenStreamRecognitionException) e).recog); + } + return processor.getExceptionTips(e); } - return StringUtils.EMPTY; } } diff --git a/designer-base/src/main/java/com/fr/design/formula/FormulaPane.java b/designer-base/src/main/java/com/fr/design/formula/FormulaPane.java index 6012bba19..9fa1af23e 100644 --- a/designer-base/src/main/java/com/fr/design/formula/FormulaPane.java +++ b/designer-base/src/main/java/com/fr/design/formula/FormulaPane.java @@ -16,7 +16,13 @@ import com.fr.design.dialog.BasicPane; import com.fr.design.dialog.DialogActionAdapter; import com.fr.design.dialog.FineJOptionPane; import com.fr.design.file.HistoryTemplateListCache; +import com.fr.design.gui.autocomplete.CompletionCellRenderer; +import com.fr.design.gui.autocomplete.CompletionProvider; +import com.fr.design.gui.autocomplete.DefaultCompletionProvider; +import com.fr.design.gui.autocomplete.FormulaCompletion; +import com.fr.design.gui.autocomplete.FormulaPaneAutoCompletion; import com.fr.design.gui.ibutton.UIButton; +import com.fr.design.gui.icheckbox.UICheckBox; import com.fr.design.gui.icontainer.UIScrollPane; import com.fr.design.gui.ilable.UILabel; import com.fr.design.gui.ilist.QuickList; @@ -43,6 +49,7 @@ import com.fr.parser.SheetIntervalLiteral; import com.fr.report.core.namespace.SimpleCellValueNameSpace; import com.fr.script.Calculator; import com.fr.script.ScriptConstants; +import com.fr.script.checker.result.FormulaCheckResult; import com.fr.stable.EncodeConstants; import com.fr.stable.EssentialUtils; import com.fr.stable.ParameterProvider; @@ -65,6 +72,7 @@ import javax.swing.JList; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; +import javax.swing.JScrollPane; import javax.swing.JTree; import javax.swing.SwingUtilities; import javax.swing.event.ListSelectionEvent; @@ -82,6 +90,8 @@ import java.awt.Component; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; @@ -112,17 +122,27 @@ import java.util.Set; * @since 2012-3-29下午1:50:53 */ public class FormulaPane extends BasicPane implements KeyListener, UIFormula { - - public static final String VALID_FORMULA = Toolkit.i18nText("Fine-Design_Basic_FormulaD_Valid_Formula"); - public static final String INVALID_FORMULA = Toolkit.i18nText("Fine-Design_Basic_FormulaD_Invalid_Formula"); + private final static String CONFIRM_DIALOG_TITLE = com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Confirm"); public static final int DEFUAL_FOMULA_LENGTH = 103; public static final String ELLIPSIS = "..."; + public static final int KEY_CODE_A = 64; + public static final int KEY_CODE_Z = 91; + public static final String CHECK_RESULT = Toolkit.i18nText("Fine-Design_Basic_Formula_Check_Result"); + public static final String CONTINUE = Toolkit.i18nText("Fine-Design_Basic_Formula_Continue"); + public static final String CAL_RESULT = Toolkit.i18nText("Fine-Design_Basic_Formula_Cal_Result"); + public static final String NEWLINE = "\n"; + public static final String ERROR_POSITION = Toolkit.i18nText("Fine-Design_Basic_Formula_Error_Position"); + public static final String FORMULA_ICON = "/com/fr/design/images/m_file/formula.png"; + public static final String PARAM_ICON = "/com/fr/design/images/m_file/param.png"; + public static final int KEY_CODE_$ = 515; private VariableTreeAndDescriptionArea variableTreeAndDescriptionArea; private RSyntaxTextArea formulaTextArea; private UITextField keyWordTextField = new UITextField(18); private int currentPosition = 0; private int beginPosition = 0; private int insertPosition = 0; + protected static UICheckBox autoCompletionCheck; + protected static UICheckBox checkBeforeColse; private JList tipsList; protected DefaultListModel listModel = new DefaultListModel(); private int ifHasBeenWriten = 0; @@ -131,6 +151,9 @@ public class FormulaPane extends BasicPane implements KeyListener, UIFormula { private DefaultListModel functionNameModel; private JList functionNameList; private UITableEditorPane editor4CalPane; + private FormulaPaneAutoCompletion autoCompletion; + private static DefaultCompletionProvider COMPLETION_PROVIDER = null; + private static final Map PARAM_PREFIX_MAP = new HashMap<>(); public FormulaPane() { initComponents(); @@ -139,6 +162,21 @@ public class FormulaPane extends BasicPane implements KeyListener, UIFormula { private void initFormulaTextAreaKeyListener() { formulaTextArea.addKeyListener(this); formulaTextArea.addKeyListener(new KeyAdapter() { + //用来判断一下是不是组合键 + + @Override + public void keyTyped(KeyEvent e) { + if (inKeyCodeRange(e) && autoCompletionCheck.isSelected()) { + autoCompletion.doCompletion(); + } + } + + private boolean inKeyCodeRange(KeyEvent e) { + //|| e.getExtendedKeyCode() == + return (e.getExtendedKeyCode() > KEY_CODE_A && e.getExtendedKeyCode() < KEY_CODE_Z); + } + + @Override public void keyReleased(KeyEvent e) { formulaTextArea.setForeground(Color.black); String text = formulaTextArea.getText(); @@ -150,6 +188,9 @@ public class FormulaPane extends BasicPane implements KeyListener, UIFormula { insertPosition = 0; formulaTextArea.setText(text); } + if (e.getExtendedKeyCode() == KEY_CODE_$) { + autoCompletion.doCompletion(); + } } }); } @@ -174,7 +215,9 @@ public class FormulaPane extends BasicPane implements KeyListener, UIFormula { beginPosition = getBeginPosition(); insertPosition = beginPosition; firstStepToFindTips(beginPosition); - fixFunctionNameList(); + if (tipsList.getSelectedValue() != null) { + fixFunctionNameList(tipsList.getSelectedValue().toString()); + } } } }); @@ -204,7 +247,9 @@ public class FormulaPane extends BasicPane implements KeyListener, UIFormula { if (e.getKeyCode() == KeyEvent.VK_ENTER) { String toFind = keyWordTextField.getText(); search(toFind, false); - fixFunctionNameList(); + if (tipsList.getSelectedValue() != null) { + fixFunctionNameList(tipsList.getSelectedValue().toString()); + } e.consume(); } } @@ -213,28 +258,26 @@ public class FormulaPane extends BasicPane implements KeyListener, UIFormula { private void initTipsPane() { // tipsPane - JPanel tipsPane = new JPanel(new BorderLayout(4, 4)); - this.add(tipsPane, BorderLayout.EAST); - JPanel searchPane = new JPanel(new BorderLayout(4, 4)); + JPanel searchPane = new JPanel(new BorderLayout(4, 1)); + this.add(searchPane, BorderLayout.NORTH); searchPane.add(keyWordTextField, BorderLayout.CENTER); UIButton searchButton = new UIButton(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_FormulaPane_Search")); + UILabel formulaLabel = new UILabel(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_FormulaD_Input_Formula_In_The_Text_Area_Below") + ":" + + " "); + formulaLabel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4)); searchPane.add(searchButton, BorderLayout.EAST); - tipsPane.add(searchPane, BorderLayout.NORTH); + searchPane.add(formulaLabel, BorderLayout.WEST); initKeyWordTextFieldKeyListener(); tipsList = new JList(listModel); tipsList.addMouseListener(new DoubleClick()); - UIScrollPane tipsScrollPane = new UIScrollPane(tipsList); - tipsScrollPane.setPreferredSize(new Dimension(170, 75)); - tipsScrollPane.setBorder(new UIRoundedBorder(UIConstants.LINE_COLOR, 1, UIConstants.ARC)); - tipsPane.add(tipsScrollPane, BorderLayout.CENTER); - searchButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - String toFind = keyWordTextField.getText(); - search(toFind, false); - formulaTextArea.requestFocusInWindow(); - fixFunctionNameList(); + searchButton.addActionListener(e -> { + String toFind = keyWordTextField.getText(); + search(toFind, false); + popTips(); + formulaTextArea.requestFocusInWindow(); + if (tipsList.getSelectedValue() != null) { + fixFunctionNameList(tipsList.getSelectedValue().toString()); } }); } @@ -254,18 +297,13 @@ public class FormulaPane extends BasicPane implements KeyListener, UIFormula { private void initTextPane() { // text - JPanel textPane = FRGUIPaneFactory.createBorderLayout_S_Pane(); this.add(textPane, BorderLayout.CENTER); - JPanel checkBoxandbuttonPane = FRGUIPaneFactory.createNormalFlowInnerContainer_S_Pane(); - UILabel formulaLabel = new UILabel(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_FormulaD_Input_Formula_In_The_Text_Area_Below") + ":" - + " "); - formulaLabel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4)); + JPanel checkBoxandbuttonPane = FRGUIPaneFactory.createX_AXISBoxInnerContainer_S_Pane(); initFormulaTextArea(); UIScrollPane formulaTextAreaScrollPane = new UIScrollPane(formulaTextArea); formulaTextAreaScrollPane.setBorder(null); - textPane.add(formulaLabel, BorderLayout.NORTH); textPane.add(formulaTextAreaScrollPane, BorderLayout.CENTER); textPane.add(checkBoxandbuttonPane, BorderLayout.SOUTH); @@ -275,19 +313,105 @@ public class FormulaPane extends BasicPane implements KeyListener, UIFormula { checkValidButton.addActionListener(checkValidActionListener); calButton.addActionListener(calculateActionListener); + //靠左流式布局 JPanel checkBoxPane = FRGUIPaneFactory.createNormalFlowInnerContainer_S_Pane(); - checkBoxPane.setPreferredSize(new Dimension(450, 30)); checkBoxandbuttonPane.add(checkBoxPane, BorderLayout.WEST); - checkBoxandbuttonPane.add(checkValidButton, BorderLayout.EAST); - checkBoxandbuttonPane.add(calButton, BorderLayout.EAST); + //靠右流式布局 + JPanel buttonPane = FRGUIPaneFactory.createRightFlowInnerContainer_S_Pane(); + buttonPane.add(checkValidButton, BorderLayout.EAST); + buttonPane.add(calButton, BorderLayout.EAST); + checkBoxandbuttonPane.add(buttonPane, BorderLayout.EAST); + if (autoCompletionCheck == null) { + autoCompletionCheck = new UICheckBox(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Formula_AutoCompletion")); + autoCompletionCheck.setSelected(true); + } + if (checkBeforeColse == null) { + checkBeforeColse = new UICheckBox(Toolkit.i18nText("Fine-Design_Basic_Formula_Check_Before_Closed")); + checkBeforeColse.setSelected(true); + } + checkBoxPane.add(autoCompletionCheck, BorderLayout.WEST); + checkBoxPane.add(checkBeforeColse, BorderLayout.WEST); extendCheckBoxPane(checkBoxPane); ParameterTableModel model = new ParameterTableModel(0); editor4CalPane = new UITableEditorPane<>(model); + formulaTextArea.addFocusListener(new FocusListener() { + @Override + public void focusGained(FocusEvent e) { + // 获得焦点时 安装 + if (autoCompletion == null && autoCompletionCheck.isSelected()) { + CompletionProvider provider = createCompletionProvider(); + autoCompletion = new FormulaPaneAutoCompletion(provider); + autoCompletion.setListCellRenderer(new CompletionCellRenderer()); + autoCompletion.install(formulaTextArea); + autoCompletion.installVariableTree(variableTreeAndDescriptionArea); + } + } + + @Override + public void focusLost(FocusEvent e) { + // 失去焦点时 卸载 + uninstallAutoCompletion(); + } + }); + } + + private CompletionProvider createCompletionProvider() { + if (COMPLETION_PROVIDER == null) { + COMPLETION_PROVIDER = new DefaultCompletionProvider(); + NameAndDescription[] nameAndDescriptions = FunctionConstants.ALL.getDescriptions(); + for (NameAndDescription nameAndDescription : nameAndDescriptions) { + COMPLETION_PROVIDER.addCompletion(new FormulaCompletion(COMPLETION_PROVIDER, nameAndDescription.getName(), BaseUtils.readIcon(FORMULA_ICON))); + } + + VariableResolver variableResolver = VariableResolver.DEFAULT; + List allParameters = new ArrayList<>(); + allParameters.addAll(Arrays.asList(variableResolver.resolveCurReportVariables())); + allParameters.addAll(Arrays.asList(variableResolver.resolveColumnNames())); + allParameters.addAll(Arrays.asList(variableResolver.resolveGlobalParameterVariables())); + allParameters.addAll(Arrays.asList(variableResolver.resolveReportParameterVariables())); + allParameters.addAll(Arrays.asList(variableResolver.resolveTableDataParameterVariables())); + + //先把参数前缀拿出来 + for (String parameter : allParameters) { + String paramWithoutPre; + if (parameter.startsWith("$$")) { + paramWithoutPre = parameter.substring(2); + PARAM_PREFIX_MAP.put(paramWithoutPre, "$$"); + } else if (parameter.startsWith("$")) { + paramWithoutPre = parameter.substring(1); + PARAM_PREFIX_MAP.put(paramWithoutPre, "$"); + } else { + paramWithoutPre = parameter; + PARAM_PREFIX_MAP.put(paramWithoutPre, StringUtils.EMPTY); + } + COMPLETION_PROVIDER.addCompletion(new FormulaCompletion(COMPLETION_PROVIDER, paramWithoutPre, BaseUtils.readIcon(PARAM_ICON))); + } + COMPLETION_PROVIDER.addCompletion(new FormulaCompletion(COMPLETION_PROVIDER, "$$$", BaseUtils.readIcon(PARAM_ICON))); + + return COMPLETION_PROVIDER; + } + return COMPLETION_PROVIDER; + } + + public static boolean containsParam(String param) { + return PARAM_PREFIX_MAP.containsKey(param); + } + + public static String getParamPrefix(String param) { + return PARAM_PREFIX_MAP.getOrDefault(param, StringUtils.EMPTY); + } + + private void uninstallAutoCompletion() { + if (autoCompletion != null) { + autoCompletion.uninstall(); + autoCompletion = null; + } } protected void extendCheckBoxPane(JPanel checkBoxPane) { + // do nothing } @@ -341,7 +465,9 @@ public class FormulaPane extends BasicPane implements KeyListener, UIFormula { private void singleClickActuator(String currentLineContent) { refreshDescriptionTextArea(currentLineContent); formulaTextArea.requestFocusInWindow(); - fixFunctionNameList(); + if (tipsList.getSelectedValue() != null) { + fixFunctionNameList(tipsList.getSelectedValue().toString()); + } } private void doubleClickActuator(String currentLineContent) { @@ -405,43 +531,41 @@ public class FormulaPane extends BasicPane implements KeyListener, UIFormula { beginPosition = getBeginPosition(); insertPosition = beginPosition; firstStepToFindTips(beginPosition); - fixFunctionNameList(); + if (tipsList.getSelectedValue() != null) { + fixFunctionNameList(tipsList.getSelectedValue().toString()); + } ifHasBeenWriten = 1; } } } - private void fixFunctionNameList() { - if (tipsList.getSelectedValue() != null) { - int signOfContinue = 1; - int indexOfFunction = 0; - for (int i = 0; i < functionTypeListModel.size(); i++) { - int signOfType = 0; - FunctionGroup functionType = (FunctionGroup) functionTypeListModel.getElementAt(i); - NameAndDescription[] nads = functionType.getDescriptions(); - if (signOfContinue == 1) { - functionNameModel.removeAllElements(); - String functionName = ((String) tipsList.getSelectedValue()); - for (int k = 0; k < nads.length; k++) { - functionNameModel.addElement(nads[k]); - if (functionName.equals(nads[k].getName()))//若相等,找出显示的函数的index,setSelectedIndex() - { - signOfType = 1; - signOfContinue = 0; - indexOfFunction = k; - } + private void fixFunctionNameList(String functionName) { + int signOfContinue = 1; + int indexOfFunction = 0; + for (int i = 0; i < functionTypeListModel.size(); i++) { + int signOfType = 0; + FunctionGroup functionType = (FunctionGroup) functionTypeListModel.getElementAt(i); + NameAndDescription[] nads = functionType.getDescriptions(); + if (signOfContinue == 1) { + functionNameModel.removeAllElements(); + for (int k = 0; k < nads.length; k++) { + functionNameModel.addElement(nads[k]); + if (functionName.equals(nads[k].getName()))//若相等,找出显示的函数的index,setSelectedIndex() + { + signOfType = 1; + signOfContinue = 0; + indexOfFunction = k; } + } - if (signOfType == 1) { - functionTypeList.setSelectedIndex(i); - signOfType = 0; - } + if (signOfType == 1) { + functionTypeList.setSelectedIndex(i); + signOfType = 0; } } - functionNameList.setSelectedIndex(indexOfFunction); - functionNameList.ensureIndexIsVisible(indexOfFunction); } - + functionNameList.setSelectedIndex(indexOfFunction); + functionNameList.ensureIndexIsVisible(indexOfFunction); } private int getBeginPosition() { @@ -492,9 +616,17 @@ public class FormulaPane extends BasicPane implements KeyListener, UIFormula { // do nothing } + private void popTips() { + JPopupMenu popupMenu = new JPopupMenu(); + JScrollPane tipsScrollPane = new JScrollPane(tipsList); + popupMenu.add(tipsScrollPane); + tipsScrollPane.setPreferredSize(new Dimension(240, 146)); + tipsScrollPane.setBorder(new UIRoundedBorder(UIConstants.LINE_COLOR, 1, UIConstants.ARC)); + popupMenu.show(keyWordTextField, 0, 23); + } + protected void search(String keyWord, boolean findDescription) { listModel.removeAllElements(); - keyWord = removeAllSpace(keyWord); if (keyWord.length() != 0) { NameAndDescription[] descriptions = FunctionConstants.ALL.getDescriptions(); @@ -610,7 +742,9 @@ public class FormulaPane extends BasicPane implements KeyListener, UIFormula { beginPosition = getBeginPosition(); insertPosition = beginPosition; firstStepToFindTips(beginPosition); - fixFunctionNameList(); + if (tipsList.getSelectedValue() != null) { + fixFunctionNameList(tipsList.getSelectedValue().toString()); + } ifHasBeenWriten = 1; } else { this.formulaTextArea.setText(content); @@ -618,7 +752,9 @@ public class FormulaPane extends BasicPane implements KeyListener, UIFormula { beginPosition = getBeginPosition(); insertPosition = beginPosition; firstStepToFindTips(beginPosition); - fixFunctionNameList(); + if (tipsList.getSelectedValue() != null) { + fixFunctionNameList(tipsList.getSelectedValue().toString()); + } ifHasBeenWriten = 1; } } @@ -650,17 +786,12 @@ public class FormulaPane extends BasicPane implements KeyListener, UIFormula { public void actionPerformed(ActionEvent evt) { // Execute Formula default cell element. String formulaText = formulaTextArea.getText().trim(); - String formulaValidMessage; - try { - formulaValidMessage = FormulaChecker.check(formulaText); - showMessageDialog(formulaValidMessage + "."); - } catch (FormulaCheckerException e) { - formulaValidMessage = e.getMessage(); - showMessageDialog(formulaValidMessage + ".", false); - } + FormulaCheckResult checkResult = FormulaChecker.check(formulaText); + confirmCheckResult(checkResult); } }; + private final ActionListener calculateActionListener = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { @@ -671,20 +802,10 @@ public class FormulaPane extends BasicPane implements KeyListener, UIFormula { return; } - String formulaValidMessage; - boolean formulaValid; - try { - formulaValidMessage = FormulaChecker.check(formulaText); - formulaValid = true; - } catch (FormulaCheckerException formulaCheckerException) { - formulaValidMessage = formulaCheckerException.getMessage(); - formulaValid = false; - } String messageTips; - if (ComparatorUtils.equals(formulaValidMessage, INVALID_FORMULA)) { - messageTips = INVALID_FORMULA; - } else { - messageTips = ComparatorUtils.equals(formulaValidMessage, VALID_FORMULA) ? "" : formulaValidMessage + "\n"; + FormulaCheckResult checkResult = FormulaChecker.check(formulaText); + if (checkResult.isValid()) { + messageTips = checkResult.getTips() + NEWLINE; Map paramsMap = setParamsIfExist(formulaText); Calculator calculator = Calculator.createCalculator(); ParameterMapNameSpace parameterMapNameSpace = ParameterMapNameSpace.create(paramsMap); @@ -704,19 +825,42 @@ public class FormulaPane extends BasicPane implements KeyListener, UIFormula { String objectToString = EssentialUtils.objectToString(value); String result = objectToString.length() > DEFUAL_FOMULA_LENGTH ? objectToString.substring(0, DEFUAL_FOMULA_LENGTH - ELLIPSIS.length()) + ELLIPSIS : objectToString; - messageTips = messageTips + - Toolkit.i18nText("Fine-Design_Basic_Formula_Cal_Result") + ":" + result; + messageTips = messageTips + CAL_RESULT + ":" + result; FineLoggerFactory.getLogger().info("value:{}", value); } catch (UtilEvalError utilEvalError) { - FineLoggerFactory.getLogger().error("", utilEvalError); + FineLoggerFactory.getLogger().error(utilEvalError.getMessage(), utilEvalError); } + } else { + messageTips = checkResult.getTips(); + } + if (checkResult.isValid()) { + showMessageDialog(messageTips, checkResult.isValid()); + } else { + confirmCheckResult(checkResult); } - showMessageDialog(messageTips, formulaValid); } }; - private void showMessageDialog(String message) { - showMessageDialog(message, true); + private boolean confirmCheckResult(FormulaCheckResult checkResult) { + if (checkResult.isValid()) { + showMessageDialog(checkResult.getTips(), checkResult.isValid()); + } else { + int confirmDialog = FineJOptionPane.showConfirmDialog( + FormulaPane.this, + checkResult.getTips() + ", " + ERROR_POSITION + ":" + (checkResult.getFormulaCoordinates().getColumns()) + ".", + CONFIRM_DIALOG_TITLE, + JOptionPane.YES_NO_OPTION, + JOptionPane.WARNING_MESSAGE, + null, + new String[]{CHECK_RESULT, CONTINUE}, + CHECK_RESULT); + if (confirmDialog == 0) { + formulaTextArea.setCaretPosition(checkResult.getFormulaCoordinates().getColumns()); + formulaTextArea.requestFocus(); + return false; + } + } + return true; } private void showMessageDialog(String message, boolean formulaValid) { @@ -750,6 +894,18 @@ public class FormulaPane extends BasicPane implements KeyListener, UIFormula { return null; } + @Override + public boolean confirmContinueBeforeDoOK() { + if (checkBeforeColse.isSelected()) { + String formula = formulaTextArea.getText().trim(); + FormulaCheckResult checkResult = FormulaChecker.check(formula); + if (!checkResult.isValid()) { + return confirmCheckResult(checkResult); + } + } + return true; + } + private Map setParamsIfExist(String formulaText) { Map parameterMap = new HashMap<>(); try { @@ -990,7 +1146,8 @@ public class FormulaPane extends BasicPane implements KeyListener, UIFormula { private void initDescriptionTextArea() { // Description - descriptionTextArea = new UITextArea(16, 27); + descriptionTextArea = new UITextArea(); + descriptionTextArea.setPreferredSize(new Dimension(350, 200)); UIScrollPane desScrollPane = new UIScrollPane(descriptionTextArea); desScrollPane.setBorder(null); @@ -1191,6 +1348,10 @@ public class FormulaPane extends BasicPane implements KeyListener, UIFormula { } }; + public void refreshText(String line) { + refreshDescriptionTextArea(line); + } + public void populate(VariableResolver variableResolver) { // varibale tree. DefaultTreeModel variableModel = (DefaultTreeModel) variablesTree.getModel(); @@ -1326,12 +1487,12 @@ public class FormulaPane extends BasicPane implements KeyListener, UIFormula { buffer.append(name.toUpperCase()); buffer.append("\""); buffer.append("|"); - buffer.append("\n"); + buffer.append(NEWLINE); buffer.append("\""); buffer.append(name.toLowerCase()); buffer.append("\""); buffer.append("|"); - buffer.append("\n"); + buffer.append(NEWLINE); } FineLoggerFactory.getLogger().debug(buffer.toString()); } diff --git a/designer-base/src/main/java/com/fr/design/formula/FormulaPaneWhenReserveFormula.java b/designer-base/src/main/java/com/fr/design/formula/FormulaPaneWhenReserveFormula.java index e08f7e3b4..ab49b605d 100644 --- a/designer-base/src/main/java/com/fr/design/formula/FormulaPaneWhenReserveFormula.java +++ b/designer-base/src/main/java/com/fr/design/formula/FormulaPaneWhenReserveFormula.java @@ -3,11 +3,10 @@ package com.fr.design.formula; import com.fr.base.BaseFormula; import com.fr.design.gui.icheckbox.UICheckBox; - -import javax.swing.*; +import javax.swing.JPanel; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; -import java.awt.*; +import java.awt.BorderLayout; /** * @author richie @@ -41,8 +40,8 @@ public class FormulaPaneWhenReserveFormula extends FormulaPane { reserveCheckBox4Write = new UICheckBox(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Write_Save_Formula")); reserveCheckBox4Write.setSelected(false); - checkBoxPane.add(reserveCheckBox4Result, BorderLayout.CENTER); - checkBoxPane.add(reserveCheckBox4Write, BorderLayout.SOUTH); + checkBoxPane.add(reserveCheckBox4Result, BorderLayout.WEST); + checkBoxPane.add(reserveCheckBox4Write, BorderLayout.WEST); } @Override diff --git a/designer-base/src/main/java/com/fr/design/formula/exception/FormulaExceptionTipsProcessor.java b/designer-base/src/main/java/com/fr/design/formula/exception/FormulaExceptionTipsProcessor.java new file mode 100644 index 000000000..44f3d59b4 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/formula/exception/FormulaExceptionTipsProcessor.java @@ -0,0 +1,48 @@ +package com.fr.design.formula.exception; + +import com.fr.design.formula.FormulaChecker; +import com.fr.design.formula.exception.function.FormulaCheckWrongFunction; +import com.fr.design.formula.exception.function.MismatchedCharFunction; +import com.fr.design.formula.exception.function.MismatchedTokenFunction; +import com.fr.design.formula.exception.function.NoViableAltForCharFunction; +import com.fr.design.formula.exception.function.NoViableAltFunction; +import com.fr.script.checker.exception.FunctionCheckWrongException; +import com.fr.script.checker.result.FormulaCheckResult; +import com.fr.script.checker.result.FormulaCoordinates; +import com.fr.third.antlr.MismatchedCharException; +import com.fr.third.antlr.MismatchedTokenException; +import com.fr.third.antlr.NoViableAltException; +import com.fr.third.antlr.NoViableAltForCharException; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; + +/** + * @author Hoky + * @date 2021/10/26 + */ +public class FormulaExceptionTipsProcessor { + private static final Map> EXCEPTION_TIPS = new ConcurrentHashMap<>(); + + private static final FormulaExceptionTipsProcessor PROCESSOR = new FormulaExceptionTipsProcessor(); + + static { + EXCEPTION_TIPS.put(FunctionCheckWrongException.class, FormulaCheckWrongFunction.getFunction()); + EXCEPTION_TIPS.put(MismatchedCharException.class, MismatchedCharFunction.getFunction()); + EXCEPTION_TIPS.put(MismatchedTokenException.class, MismatchedTokenFunction.getFunction()); + EXCEPTION_TIPS.put(NoViableAltException.class, NoViableAltFunction.getFunction()); + EXCEPTION_TIPS.put(NoViableAltForCharException.class, NoViableAltForCharFunction.getFunction()); + + } + + public FormulaCheckResult getExceptionTips(Exception e) { + return EXCEPTION_TIPS.getOrDefault(e.getClass(), + e1 -> new FormulaCheckResult(false, FormulaChecker.INVALID_FORMULA, FormulaCoordinates.INVALID)) + .apply(e); + } + + public static FormulaExceptionTipsProcessor getProcessor() { + return PROCESSOR; + } +} diff --git a/designer-base/src/main/java/com/fr/design/formula/exception/function/FormulaCheckConstants.java b/designer-base/src/main/java/com/fr/design/formula/exception/function/FormulaCheckConstants.java new file mode 100644 index 000000000..6e3af75c5 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/formula/exception/function/FormulaCheckConstants.java @@ -0,0 +1,31 @@ +package com.fr.design.formula.exception.function; + +import com.fr.design.i18n.Toolkit; + +/** + * @author Hoky + * @date 2021/10/28 + */ +public class FormulaCheckConstants { + public final static String EXPECTING = Toolkit.i18nText("Fine-Design_Basic_Formula_Check_Expecting") + ": "; + public final static String FOUND = Toolkit.i18nText("Fine-Design_Basic_Formula_Check_Found"); + public final static String EXPECTING_ANYTHING = Toolkit.i18nText("Fine-Design_Basic_Formula_Check_Expecting_Anything"); + public final static String GOT_IT_ANYWAY = Toolkit.i18nText("Fine-Design_Basic_Formula_Check_GotItAnyway"); + public final static String TOKEN = Toolkit.i18nText("Fine-Design_Basic_Formula_Check_Token"); + public final static String IN_RANGE = Toolkit.i18nText("Fine-Design_Basic_Formula_Check_In_Range"); + public final static String CHECK_NOT = Toolkit.i18nText("Fine-Design_Basic_Formula_Check_Not"); + public final static String ONE_OF = Toolkit.i18nText("Fine-Design_Basic_Formula_Check_ONE_OF"); + public final static String UNEXPECTED_TOKEN = Toolkit.i18nText("Fine-Design_Basic_Formula_Check_Unexpected_Token"); + public final static String UNEXPECTED_CHAR = Toolkit.i18nText("Fine-Design_Basic_Formula_Check_Unexpected_Char"); + public final static String UNEXPECTED_END_OF_SUBTREE = Toolkit.i18nText("Fine-Design_Basic_Formula_Check_End_Of_Subtree"); + public final static String UNEXPECTED_AST_NODE = Toolkit.i18nText("Fine-Design_Basic_Formula_Check_Unexpected_AST_Node"); + public final static String MISMATCHED_TOKEN = Toolkit.i18nText("Fine-Design_Basic_Formula_Check_Mismatched_Token"); + public final static String MISMATCHED_CHAR = Toolkit.i18nText("Fine-Design_Basic_Formula_Check_Mismatched_Char"); + public final static String MISMATCHED_EOF = Toolkit.i18nText("Fine-Design_Basic_Formula_Check_Mismatched_EOF"); + public static final String COLON = ":"; + public static final String LEFT = "("; + public static final String COMMON = ","; + public static final String RIGHT = ")"; + public static final String BLANK = " "; + public static final String SINGLE_QUOTES = "'"; +} diff --git a/designer-base/src/main/java/com/fr/design/formula/exception/function/FormulaCheckWrongFunction.java b/designer-base/src/main/java/com/fr/design/formula/exception/function/FormulaCheckWrongFunction.java new file mode 100644 index 000000000..274868989 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/formula/exception/function/FormulaCheckWrongFunction.java @@ -0,0 +1,74 @@ +package com.fr.design.formula.exception.function; + +import com.fr.design.i18n.Toolkit; +import com.fr.script.checker.exception.FunctionCheckWrongException; +import com.fr.script.checker.result.FormulaCheckResult; +import com.fr.script.checker.result.FormulaCoordinates; +import com.fr.script.rules.FunctionParameterType; +import com.fr.script.rules.FunctionRule; +import com.fr.stable.StringUtils; + +import java.util.List; +import java.util.function.Function; + +/** + * @author Hoky + * @date 2021/10/26 + */ +public class FormulaCheckWrongFunction implements Function { + private final static FormulaCheckWrongFunction FUNCTION = new FormulaCheckWrongFunction(); + + @Override + public FormulaCheckResult apply(Exception e) { + if (e instanceof FunctionCheckWrongException) { + FunctionCheckWrongException ce = (FunctionCheckWrongException) e; + List rules = ce.getRules(); + String functionName = ce.getFunctionName(); + StringBuilder errorMsg = new StringBuilder(functionName + Toolkit.i18nText("Fine-Design_Basic_Formula_Check_Error_Tips") + FormulaCheckConstants.COLON); + for (int i = 0; i < rules.size(); i++) { + errorMsg.append(FormulaCheckConstants.LEFT); + if (rules.get(i).getParameterList().isEmpty()) { + errorMsg.append(Toolkit.i18nText("Fine-Design_Basic_Formula_No_Param")); + } + for (FunctionParameterType functionParameterType : rules.get(i).getParameterList()) { + errorMsg.append(getTypeString(functionParameterType)).append(FormulaCheckConstants.COMMON); + } + if (FormulaCheckConstants.COMMON.equals(errorMsg.charAt(errorMsg.length() - 1) + StringUtils.EMPTY)) { + errorMsg.deleteCharAt(errorMsg.length() - 1); + } + errorMsg.append(FormulaCheckConstants.RIGHT); + if (i != rules.size() - 1) { + errorMsg.append(Toolkit.i18nText("Fine-Design_Basic_Formula_Check_Or")); + } + } + return new FormulaCheckResult(false, errorMsg.toString(), new FormulaCoordinates(1, indexPosition(ce.getFormulaText(), ce.getNode().toString()))); + } + return new FormulaCheckResult(false, StringUtils.EMPTY, new FormulaCoordinates(-1, -1)); + } + + private static String getTypeString(FunctionParameterType type) { + switch (type) { + case NUMBER: + return Toolkit.i18nText("Fine-Design_Basic_Formula_Check_ParamType_Number"); + case STRING: + return Toolkit.i18nText("Fine-Design_Basic_Formula_Check_ParamType_String"); + case ANY: + return Toolkit.i18nText("Fine-Design_Basic_Formula_Check_ParamType_Any"); + case DATE: + return Toolkit.i18nText("Fine-Design_Basic_Formula_Check_ParamType_Date"); + case BOOLEAN: + return Toolkit.i18nText("Fine-Design_Basic_Formula_Check_ParamType_Boolean"); + case ARRAY: + return Toolkit.i18nText("Fine-Design_Basic_Formula_Check_ParamType_Array"); + } + return StringUtils.EMPTY; + } + + public static Function getFunction() { + return FUNCTION; + } + + private int indexPosition(String formulaText, String invalidFormula) { + return formulaText.indexOf(invalidFormula); + } +} diff --git a/designer-base/src/main/java/com/fr/design/formula/exception/function/MismatchedCharFunction.java b/designer-base/src/main/java/com/fr/design/formula/exception/function/MismatchedCharFunction.java new file mode 100644 index 000000000..e63353a08 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/formula/exception/function/MismatchedCharFunction.java @@ -0,0 +1,103 @@ +package com.fr.design.formula.exception.function; + +import com.fr.design.formula.FormulaChecker; +import com.fr.script.checker.result.FormulaCheckResult; +import com.fr.script.checker.result.FormulaCoordinates; +import com.fr.third.antlr.MismatchedCharException; + +import java.util.function.Function; + +/** + * @author Hoky + * @date 2021/10/28 + */ +public class MismatchedCharFunction implements Function { + private final static MismatchedCharFunction FUNCTION = new MismatchedCharFunction(); + + @Override + public FormulaCheckResult apply(Exception e) { + if (e instanceof MismatchedCharException) { + MismatchedCharException charException = (MismatchedCharException) e; + FormulaCoordinates formulaCoordinates = new FormulaCoordinates(charException.line, charException.column - 1); + return new FormulaCheckResult(false, getMessage(charException), formulaCoordinates); + } + return new FormulaCheckResult(false, FormulaChecker.INVALID_FORMULA, FormulaCoordinates.INVALID); + } + + public static Function getFunction() { + return FUNCTION; + } + + private String getMessage(MismatchedCharException charException) { + StringBuffer sb = new StringBuffer(); + switch (charException.mismatchType) { + case 1: + sb.append(FormulaCheckConstants.EXPECTING); + appendCharName(sb, charException.expecting); + sb.append(FormulaCheckConstants.COMMON) + .append(FormulaCheckConstants.BLANK) + .append(FormulaCheckConstants.FOUND); + appendCharName(sb, charException.foundChar); + break; + case 2: + sb.append(FormulaCheckConstants.EXPECTING_ANYTHING) + .append(FormulaCheckConstants.BLANK) + .append(FormulaCheckConstants.SINGLE_QUOTES); + appendCharName(sb, charException.expecting); + sb.append("';").append(FormulaCheckConstants.GOT_IT_ANYWAY); + break; + case 3: + case 4: + sb.append(FormulaCheckConstants.EXPECTING).append(FormulaCheckConstants.TOKEN); + if (charException.mismatchType == 4) { + sb.append(FormulaCheckConstants.CHECK_NOT); + } + + sb.append(FormulaCheckConstants.IN_RANGE).append(": "); + appendCharName(sb, charException.expecting); + sb.append(".."); + appendCharName(sb, charException.upper); + sb.append(", ").append(FormulaCheckConstants.FOUND); + appendCharName(sb, charException.foundChar); + break; + case 5: + case 6: + sb.append(FormulaCheckConstants.EXPECTING) + .append(charException.mismatchType == 6 ? FormulaCheckConstants.CHECK_NOT : FormulaCheckConstants.BLANK) + .append(FormulaCheckConstants.ONE_OF).append(" ("); + int[] elems = charException.set.toArray(); + + for (int i = 0; i < elems.length; ++i) { + appendCharName(sb, elems[i]); + } + + sb.append("), ").append(FormulaCheckConstants.FOUND); + appendCharName(sb, charException.foundChar); + break; + default: + sb.append(FormulaCheckConstants.MISMATCHED_CHAR); + } + + return sb.toString(); + } + + private void appendCharName(StringBuffer sb, int c) { + switch (c) { + case 9: + sb.append("'\\t'"); + break; + case 10: + sb.append("'\\n'"); + break; + case 13: + sb.append("'\\r'"); + break; + case 65535: + sb.append(FormulaCheckConstants.MISMATCHED_EOF); + break; + default: + sb.append((char) c); + } + + } +} diff --git a/designer-base/src/main/java/com/fr/design/formula/exception/function/MismatchedTokenFunction.java b/designer-base/src/main/java/com/fr/design/formula/exception/function/MismatchedTokenFunction.java new file mode 100644 index 000000000..eb825e3b2 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/formula/exception/function/MismatchedTokenFunction.java @@ -0,0 +1,139 @@ +package com.fr.design.formula.exception.function; + +import com.fr.design.formula.FormulaChecker; +import com.fr.log.FineLoggerFactory; +import com.fr.script.checker.result.FormulaCheckResult; +import com.fr.script.checker.result.FormulaCoordinates; +import com.fr.stable.StringUtils; +import com.fr.third.antlr.MismatchedTokenException; + +import java.lang.reflect.Field; +import java.util.function.Function; + +/** + * @author Hoky + * @date 2021/10/28 + */ +public class MismatchedTokenFunction implements Function { + private final static MismatchedTokenFunction FUNCTION = new MismatchedTokenFunction(); + public static final String NULL_STRING = "null"; + + @Override + public FormulaCheckResult apply(Exception e) { + if (e instanceof MismatchedTokenException) { + MismatchedTokenException charException = (MismatchedTokenException) e; + FormulaCoordinates formulaCoordinates = new FormulaCoordinates(charException.line, charException.column - 1); + return new FormulaCheckResult(false, getMessage(charException), formulaCoordinates); + } + return new FormulaCheckResult(false, FormulaChecker.INVALID_FORMULA, FormulaCoordinates.INVALID); + } + + public static Function getFunction() { + return FUNCTION; + } + + public String getMessage(MismatchedTokenException exception) { + StringBuilder sb = new StringBuilder(); + Object fieldValue = getFieldValue(exception, "tokenText"); + String tokenText = fieldValue == null ? NULL_STRING : fieldValue.toString(); + switch (exception.mismatchType) { + case 1: + sb.append(FormulaCheckConstants.EXPECTING) + .append(tokenName(exception.expecting, exception)) + .append(", ") + .append(FormulaCheckConstants.FOUND) + .append(FormulaCheckConstants.BLANK + FormulaCheckConstants.SINGLE_QUOTES) + .append(tokenText).append("'"); + break; + case 2: + sb.append(FormulaCheckConstants.EXPECTING_ANYTHING) + .append(tokenName(exception.expecting, exception)) + .append("; ") + .append(FormulaCheckConstants.GOT_IT_ANYWAY); + break; + case 3: + sb.append(FormulaCheckConstants.EXPECTING) + .append(FormulaCheckConstants.TOKEN) + .append(FormulaCheckConstants.IN_RANGE) + .append(": ") + .append(tokenName(exception.expecting, exception)) + .append("..") + .append(tokenName(exception.upper, exception)) + .append(", ").append(FormulaCheckConstants.FOUND) + .append("'").append(tokenText).append("'"); + break; + case 4: + sb.append(FormulaCheckConstants.EXPECTING) + .append(FormulaCheckConstants.TOKEN) + .append(FormulaCheckConstants.CHECK_NOT) + .append(FormulaCheckConstants.IN_RANGE) + .append(": ") + .append(tokenName(exception.expecting, exception)) + .append("..") + .append(tokenName(exception.upper, exception)) + .append(","). + append(FormulaCheckConstants.FOUND) + .append(" '") + .append(tokenText) + .append("'"); + break; + case 5: + case 6: + sb.append(FormulaCheckConstants.EXPECTING) + .append(exception.mismatchType == 6 ? FormulaCheckConstants.CHECK_NOT : FormulaCheckConstants.BLANK) + .append(FormulaCheckConstants.ONE_OF).append("("); + int[] elms = exception.set.toArray(); + + for (int i = 0; i < elms.length; ++i) { + sb.append(' '); + sb.append(tokenName(elms[i], exception)); + } + + sb.append("),") + .append(FormulaCheckConstants.FOUND) + .append("'") + .append(tokenText) + .append("'"); + break; + default: + sb.append(FormulaCheckConstants.MISMATCHED_TOKEN); + } + + return sb.toString(); + } + + private String tokenName(int tokenType, MismatchedTokenException exception) { + if (tokenType == 0) { + return ""; + } else { + String[] tokenNames = (String[]) getFieldValue(exception, "tokenNames"); + return tokenType >= 0 && tokenType < tokenNames.length ? translateToken(tokenNames[tokenType]) : "<" + tokenType + ">"; + } + } + + private String translateToken(String token) { + switch (token) { + case ("RPAREN"): + return ")"; + case ("LPAREN"): + return "("; + case ("COMMA"): + return ","; + case ("COLON"): + return ":"; + default: + return token; + } + } + + private Object getFieldValue(Object object, String fieldName) { + try { + Field field = object.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + return field.get(object); + } catch (Exception e) { + FineLoggerFactory.getLogger().warn(e.getMessage(), e); + return StringUtils.EMPTY; + } + } +} diff --git a/designer-base/src/main/java/com/fr/design/formula/exception/function/NoViableAltForCharFunction.java b/designer-base/src/main/java/com/fr/design/formula/exception/function/NoViableAltForCharFunction.java new file mode 100644 index 000000000..dda2577c7 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/formula/exception/function/NoViableAltForCharFunction.java @@ -0,0 +1,43 @@ +package com.fr.design.formula.exception.function; + +import com.fr.design.formula.FormulaChecker; +import com.fr.script.checker.result.FormulaCheckResult; +import com.fr.script.checker.result.FormulaCoordinates; +import com.fr.third.antlr.NoViableAltForCharException; + +import java.util.function.Function; + +/** + * @author Hoky + * @date 2021/10/28 + */ +public class NoViableAltForCharFunction implements Function { + private final static NoViableAltForCharFunction FUNCTION = new NoViableAltForCharFunction(); + + @Override + public FormulaCheckResult apply(Exception e) { + if (e instanceof NoViableAltForCharException) { + NoViableAltForCharException charException = (NoViableAltForCharException) e; + FormulaCoordinates formulaCoordinates = new FormulaCoordinates(charException.line, charException.column - 1); + return new FormulaCheckResult(false, getMessage(charException), formulaCoordinates); + } + return new FormulaCheckResult(false, FormulaChecker.INVALID_FORMULA, FormulaCoordinates.INVALID); + } + + public static Function getFunction() { + return FUNCTION; + } + + public String getMessage(NoViableAltForCharException exception) { + String mesg = FormulaCheckConstants.UNEXPECTED_CHAR + ": "; + if (exception.foundChar >= ' ' && exception.foundChar <= '~') { + mesg = mesg + '\''; + mesg = mesg + exception.foundChar; + mesg = mesg + '\''; + } else { + mesg = mesg + exception.foundChar + "(0x" + Integer.toHexString(exception.foundChar).toUpperCase() + ")"; + } + + return mesg; + } +} diff --git a/designer-base/src/main/java/com/fr/design/formula/exception/function/NoViableAltFunction.java b/designer-base/src/main/java/com/fr/design/formula/exception/function/NoViableAltFunction.java new file mode 100644 index 000000000..aa7c7d3ec --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/formula/exception/function/NoViableAltFunction.java @@ -0,0 +1,40 @@ +package com.fr.design.formula.exception.function; + +import com.fr.design.formula.FormulaChecker; +import com.fr.script.checker.result.FormulaCheckResult; +import com.fr.script.checker.result.FormulaCoordinates; +import com.fr.third.antlr.NoViableAltException; +import com.fr.third.antlr.TreeParser; + +import java.util.function.Function; + +/** + * @author Hoky + * @date 2021/10/28 + */ +public class NoViableAltFunction implements Function { + private final static NoViableAltFunction FUNCTION = new NoViableAltFunction(); + + @Override + public FormulaCheckResult apply(Exception e) { + if (e instanceof NoViableAltException) { + NoViableAltException altException = (NoViableAltException) e; + FormulaCoordinates formulaCoordinates = new FormulaCoordinates(altException.line, altException.column - 1); + return new FormulaCheckResult(false, getMessage(altException), formulaCoordinates); + } + return new FormulaCheckResult(false, FormulaChecker.INVALID_FORMULA, FormulaCoordinates.INVALID); + } + + public static Function getFunction() { + return FUNCTION; + } + + public String getMessage(NoViableAltException exception) { + if (exception.token != null) { + return FormulaCheckConstants.UNEXPECTED_TOKEN + ": " + exception.token.getText(); + } else { + return exception.node == TreeParser.ASTNULL ? FormulaCheckConstants.UNEXPECTED_END_OF_SUBTREE : + FormulaCheckConstants.UNEXPECTED_AST_NODE + ": " + exception.node.toString(); + } + } +} \ No newline at end of file diff --git a/designer-base/src/main/java/com/fr/design/gui/autocomplete/FormulaAutoCompletePopupWindow.java b/designer-base/src/main/java/com/fr/design/gui/autocomplete/FormulaAutoCompletePopupWindow.java new file mode 100644 index 000000000..e704a6bdd --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/gui/autocomplete/FormulaAutoCompletePopupWindow.java @@ -0,0 +1,1003 @@ +/* + * 12/21/2008 + * + * AutoCompletePopupWindow.java - A window containing a list of auto-complete + * choices. + * + * This library is distributed under a modified BSD license. See the included + * RSyntaxTextArea.License.txt file for details. + */ +package com.fr.design.gui.autocomplete; + +import com.fr.design.formula.FormulaPane; +import com.fr.design.gui.syntax.ui.rsyntaxtextarea.PopupWindowDecorator; +import com.fr.log.FineLoggerFactory; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.ActionMap; +import javax.swing.InputMap; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JWindow; +import javax.swing.KeyStroke; +import javax.swing.ListCellRenderer; +import javax.swing.SwingUtilities; +import javax.swing.event.CaretEvent; +import javax.swing.event.CaretListener; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.plaf.ListUI; +import javax.swing.text.Caret; +import javax.swing.text.JTextComponent; +import java.awt.BorderLayout; +import java.awt.ComponentOrientation; +import java.awt.Dimension; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.Toolkit; +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.util.List; + + +/** + * The actual popup window of choices. When visible, this window intercepts + * certain keystrokes in the parent text component and uses them to navigate + * the completion choices instead. If Enter or Escape is pressed, the window + * hides itself and notifies the {@link AutoCompletion} to insert the selected + * text. + * + * @author Robert Futrell + * @version 1.0 + */ +class FormulaAutoCompletePopupWindow extends JWindow implements CaretListener, + ListSelectionListener, MouseListener { + + private final static int DIS = 5; + /** + * The parent AutoCompletion instance. + */ + private FormulaPaneAutoCompletion ac; + + /** + * The list of completion choices. + */ + private JList list; + + /** + * The contents of {@link #list()}. + */ + private CompletionListModel model; + + /** + * A hack to work around the fact that we clear our completion model (and + * our selection) when hiding the completion window. This allows us to + * still know what the user selected after the popup is hidden. + */ + private Completion lastSelection; + + /** + * Optional popup window containing a description of the currently + * selected completion. + */ + private AutoCompleteDescWindow descWindow; + + /** + * The preferred size of the optional description window. This field + * only exists because the user may (and usually will) set the size of + * the description window before it exists (it must be parented to a + * Window). + */ + private Dimension preferredDescWindowSize; + + private FormulaPane.VariableTreeAndDescriptionArea component; + + /** + * Whether the completion window and the optional description window + * should be displayed above the current caret position (as opposed to + * underneath it, which is preferred unless there is not enough space). + */ + private boolean aboveCaret; + + private int lastLine; + + private KeyActionPair escapeKap; + private KeyActionPair upKap; + private KeyActionPair downKap; + private KeyActionPair leftKap; + private KeyActionPair rightKap; + private KeyActionPair enterKap; + private KeyActionPair tabKap; + private KeyActionPair homeKap; + private KeyActionPair endKap; + private KeyActionPair pageUpKap; + private KeyActionPair pageDownKap; + private KeyActionPair ctrlCKap; + + private KeyActionPair oldEscape, oldUp, oldDown, oldLeft, oldRight, + oldEnter, oldTab, oldHome, oldEnd, oldPageUp, oldPageDown, + oldCtrlC; + + /** + * The space between the caret and the completion popup. + */ + private static final int VERTICAL_SPACE = 1; + + /** + * The class name of the Substance List UI. + */ + private static final String SUBSTANCE_LIST_UI = + "org.pushingpixels.substance.internal.ui.SubstanceListUI"; + + + /** + * Constructor. + * + * @param parent The parent window (hosting the text component). + * @param ac The auto-completion instance. + */ + public FormulaAutoCompletePopupWindow(Window parent, final FormulaPaneAutoCompletion ac) { + + super(parent); + ComponentOrientation o = ac.getTextComponentOrientation(); + + this.ac = ac; + model = new CompletionListModel(); + list = new PopupList(model); + + list.setCellRenderer(new DelegatingCellRenderer()); + list.addListSelectionListener(this); + list.addMouseListener(this); + + JPanel contentPane = new JPanel(new BorderLayout()); + JScrollPane sp = new JScrollPane(list, + JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, + JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); + + // In 1.4, JScrollPane.setCorner() has a bug where it won't accept + // JScrollPane.LOWER_TRAILING_CORNER, even though that constant is + // defined. So we have to put the logic added in 1.5 to handle it + // here. + JPanel corner = new SizeGrip(); + //sp.setCorner(JScrollPane.LOWER_TRAILING_CORNER, corner); + boolean isLeftToRight = o.isLeftToRight(); + String str = isLeftToRight ? JScrollPane.LOWER_RIGHT_CORNER : + JScrollPane.LOWER_LEFT_CORNER; + sp.setCorner(str, corner); + + contentPane.add(sp); + setContentPane(contentPane); + applyComponentOrientation(o); + + // Give apps a chance to decorate us with drop shadows, etc. + if (Util.getShouldAllowDecoratingMainAutoCompleteWindows()) { + PopupWindowDecorator decorator = PopupWindowDecorator.get(); + if (decorator != null) { + decorator.decorate(this); + } + } + + pack(); + + setFocusableWindowState(false); + + lastLine = -1; + + } + + + public void caretUpdate(CaretEvent e) { + if (isVisible()) { // Should always be true + int line = ac.getLineOfCaret(); + if (line != lastLine) { + lastLine = -1; + setVisible(false); + } else { + doAutocomplete(); + } + } else if (AutoCompletion.isDebug()) { + Thread.dumpStack(); + } + } + + + /** + * Creates the description window. + * + * @return The description window. + */ + private AutoCompleteDescWindow createDescriptionWindow() { + AutoCompleteDescWindow dw = new AutoCompleteDescWindow(this, ac); + dw.applyComponentOrientation(ac.getTextComponentOrientation()); + Dimension size = preferredDescWindowSize; + if (size == null) { + size = getSize(); + } + dw.setSize(size); + return dw; + } + + + /** + * Creates the mappings from keys to Actions we'll be putting into the + * text component's ActionMap and InputMap. + */ + private void createKeyActionPairs() { + + // Actions we'll install. + EnterAction enterAction = new EnterAction(); + escapeKap = new KeyActionPair("Escape", new EscapeAction()); + upKap = new KeyActionPair("Up", new UpAction()); + downKap = new KeyActionPair("Down", new DownAction()); + leftKap = new KeyActionPair("Left", new LeftAction()); + rightKap = new KeyActionPair("Right", new RightAction()); + enterKap = new KeyActionPair("Enter", enterAction); + tabKap = new KeyActionPair("Tab", enterAction); + homeKap = new KeyActionPair("Home", new HomeAction()); + endKap = new KeyActionPair("End", new EndAction()); + pageUpKap = new KeyActionPair("PageUp", new PageUpAction()); + pageDownKap = new KeyActionPair("PageDown", new PageDownAction()); + ctrlCKap = new KeyActionPair("CtrlC", new CopyAction()); + + // Buffers for the actions we replace. + oldEscape = new KeyActionPair(); + oldUp = new KeyActionPair(); + oldDown = new KeyActionPair(); + oldLeft = new KeyActionPair(); + oldRight = new KeyActionPair(); + oldEnter = new KeyActionPair(); + oldTab = new KeyActionPair(); + oldHome = new KeyActionPair(); + oldEnd = new KeyActionPair(); + oldPageUp = new KeyActionPair(); + oldPageDown = new KeyActionPair(); + oldCtrlC = new KeyActionPair(); + + } + + + protected void doAutocomplete() { + lastLine = ac.refreshPopupWindow(); + } + + + /** + * Returns the copy keystroke to use for this platform. + * + * @return The copy keystroke. + */ + private static final KeyStroke getCopyKeyStroke() { + int key = KeyEvent.VK_C; + int mask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); + return KeyStroke.getKeyStroke(key, mask); + } + + + /** + * Returns the default list cell renderer used when a completion provider + * does not supply its own. + * + * @return The default list cell renderer. + * @see #setListCellRenderer(ListCellRenderer) + */ + public ListCellRenderer getListCellRenderer() { + DelegatingCellRenderer dcr = (DelegatingCellRenderer) list. + getCellRenderer(); + return dcr.getFallbackCellRenderer(); + } + + + /** + * Returns the selected value, or null if nothing is selected. + * + * @return The selected value. + */ + public Completion getSelection() { + return isShowing() ? (Completion) list.getSelectedValue() : lastSelection; + } + + + /** + * Inserts the currently selected completion. + * + * @see #getSelection() + */ + private void insertSelectedCompletion() { + Completion comp = getSelection(); + ac.insertCompletion(comp); + } + + public void installComp(FormulaPane.VariableTreeAndDescriptionArea component) { + this.component = component; + } + + private void refreshInstallComp() { + component.refreshText(getSelection().getReplacementText()); + } + + + /** + * Registers keyboard actions to listen for in the text component and + * intercept. + * + * @see #uninstallKeyBindings() + */ + private void installKeyBindings() { + + if (AutoCompletion.isDebug()) { + FineLoggerFactory.getLogger().debug("PopupWindow: Installing keybindings"); + } + + if (escapeKap == null) { // Lazily create actions. + createKeyActionPairs(); + } + + JTextComponent comp = ac.getTextComponent(); + InputMap im = comp.getInputMap(); + ActionMap am = comp.getActionMap(); + + replaceAction(im, am, KeyEvent.VK_ESCAPE, escapeKap, oldEscape); + if (AutoCompletion.isDebug() && oldEscape.action == escapeKap.action) { + Thread.dumpStack(); + } + replaceAction(im, am, KeyEvent.VK_UP, upKap, oldUp); + replaceAction(im, am, KeyEvent.VK_LEFT, leftKap, oldLeft); + replaceAction(im, am, KeyEvent.VK_DOWN, downKap, oldDown); + replaceAction(im, am, KeyEvent.VK_RIGHT, rightKap, oldRight); + replaceAction(im, am, KeyEvent.VK_ENTER, enterKap, oldEnter); + replaceAction(im, am, KeyEvent.VK_TAB, tabKap, oldTab); + replaceAction(im, am, KeyEvent.VK_HOME, homeKap, oldHome); + replaceAction(im, am, KeyEvent.VK_END, endKap, oldEnd); + replaceAction(im, am, KeyEvent.VK_PAGE_UP, pageUpKap, oldPageUp); + replaceAction(im, am, KeyEvent.VK_PAGE_DOWN, pageDownKap, oldPageDown); + + // Make Ctrl+C copy from description window. This isn't done + // automagically because the desc. window is not focusable, and copying + // from text components can only be done from focused components. + KeyStroke ks = getCopyKeyStroke(); + oldCtrlC.key = im.get(ks); + im.put(ks, ctrlCKap.key); + oldCtrlC.action = am.get(ctrlCKap.key); + am.put(ctrlCKap.key, ctrlCKap.action); + + comp.addCaretListener(this); + + } + + + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { + insertSelectedCompletion(); + refreshInstallComp(); + } else if (e.getClickCount() == 1) { + refreshInstallComp(); + } + } + + + public void mouseEntered(MouseEvent e) { + } + + + public void mouseExited(MouseEvent e) { + } + + + public void mousePressed(MouseEvent e) { + } + + + public void mouseReleased(MouseEvent e) { + } + + + /** + * Positions the description window relative to the completion choices + * window. We assume there is room on one side of the other for this + * entire window to fit. + */ + private void positionDescWindow() { + + boolean showDescWindow = descWindow != null && ac.isShowDescWindow(); + if (!showDescWindow) { + return; + } + + // Don't use getLocationOnScreen() as this throws an exception if + // window isn't visible yet, but getLocation() doesn't, and is in + // screen coordinates! + Point p = getLocation(); + Rectangle screenBounds = Util.getScreenBoundsForPoint(p.x, p.y); + //Dimension screenSize = getToolkit().getScreenSize(); + //int totalH = Math.max(getHeight(), descWindow.getHeight()); + + // Try to position to the right first (LTR) + int x; + if (ac.getTextComponentOrientation().isLeftToRight()) { + x = getX() + getWidth() + DIS; + if (x + descWindow.getWidth() > screenBounds.x + screenBounds.width) { // doesn't fit + x = getX() - DIS - descWindow.getWidth(); + } + } else { // RTL + x = getX() - DIS - descWindow.getWidth(); + if (x < screenBounds.x) { // Doesn't fit + x = getX() + getWidth() + DIS; + } + } + + int y = getY(); + if (aboveCaret) { + y = y + getHeight() - descWindow.getHeight(); + } + + if (x != descWindow.getX() || y != descWindow.getY()) { + descWindow.setLocation(x, y); + } + + } + + + /** + * "Puts back" the original key/Action pair for a keystroke. This is used + * when this popup is hidden. + * + * @param im The input map. + * @param am The action map. + * @param key The keystroke whose key/Action pair to change. + * @param kap The (original) key/Action pair. + * @see #replaceAction(InputMap, ActionMap, int, KeyActionPair, KeyActionPair) + */ + private void putBackAction(InputMap im, ActionMap am, int key, + KeyActionPair kap) { + KeyStroke ks = KeyStroke.getKeyStroke(key, 0); + am.put(im.get(ks), kap.action); // Original action for the "new" key + im.put(ks, kap.key); // Original key for the keystroke. + } + + + /** + * Replaces a key/Action pair in an InputMap and ActionMap with a new + * pair. + * + * @param im The input map. + * @param am The action map. + * @param key The keystroke whose information to replace. + * @param kap The new key/Action pair for key. + * @param old A buffer in which to place the old key/Action pair. + * @see #putBackAction(InputMap, ActionMap, int, KeyActionPair) + */ + private void replaceAction(InputMap im, ActionMap am, int key, + KeyActionPair kap, KeyActionPair old) { + KeyStroke ks = KeyStroke.getKeyStroke(key, 0); + old.key = im.get(ks); + im.put(ks, kap.key); + old.action = am.get(kap.key); + am.put(kap.key, kap.action); + } + + + /** + * Selects the first item in the completion list. + * + * @see #selectLastItem() + */ + private void selectFirstItem() { + if (model.getSize() > 0) { + list.setSelectedIndex(0); + list.ensureIndexIsVisible(0); + } + } + + + /** + * Selects the last item in the completion list. + * + * @see #selectFirstItem() + */ + private void selectLastItem() { + int index = model.getSize() - 1; + if (index > -1) { + list.setSelectedIndex(index); + list.ensureIndexIsVisible(index); + } + } + + + /** + * Selects the next item in the completion list. + * + * @see #selectPreviousItem() + */ + private void selectNextItem() { + int index = list.getSelectedIndex(); + if (index > -1) { + index = (index + 1) % model.getSize(); + list.setSelectedIndex(index); + list.ensureIndexIsVisible(index); + } + } + + + /** + * Selects the completion item one "page down" from the currently selected + * one. + * + * @see #selectPageUpItem() + */ + private void selectPageDownItem() { + int visibleRowCount = list.getVisibleRowCount(); + int i = Math.min(list.getModel().getSize() - 1, + list.getSelectedIndex() + visibleRowCount); + list.setSelectedIndex(i); + list.ensureIndexIsVisible(i); + } + + + /** + * Selects the completion item one "page up" from the currently selected + * one. + * + * @see #selectPageDownItem() + */ + private void selectPageUpItem() { + int visibleRowCount = list.getVisibleRowCount(); + int i = Math.max(0, list.getSelectedIndex() - visibleRowCount); + list.setSelectedIndex(i); + list.ensureIndexIsVisible(i); + } + + + /** + * Selects the previous item in the completion list. + * + * @see #selectNextItem() + */ + private void selectPreviousItem() { + int index = list.getSelectedIndex(); + switch (index) { + case 0: + index = list.getModel().getSize() - 1; + break; + case -1: // Check for an empty list (would be an error) + index = list.getModel().getSize() - 1; + if (index == -1) { + return; + } + break; + default: + index = index - 1; + break; + } + list.setSelectedIndex(index); + list.ensureIndexIsVisible(index); + } + + + /** + * Sets the completions to display in the choices list. The first + * completion is selected. + * + * @param completions The completions to display. + */ + public void setCompletions(List completions) { + model.setContents(completions); + selectFirstItem(); + } + + + /** + * Sets the size of the description window. + * + * @param size The new size. This cannot be null. + */ + public void setDescriptionWindowSize(Dimension size) { + if (descWindow != null) { + descWindow.setSize(size); + } else { + preferredDescWindowSize = size; + } + } + + + /** + * Sets the default list cell renderer to use when a completion provider + * does not supply its own. + * + * @param renderer The renderer to use. If this is null, + * a default renderer is used. + * @see #getListCellRenderer() + */ + public void setListCellRenderer(ListCellRenderer renderer) { + DelegatingCellRenderer dcr = (DelegatingCellRenderer) list. + getCellRenderer(); + dcr.setFallbackCellRenderer(renderer); + } + + + /** + * Sets the location of this window to be "good" relative to the specified + * rectangle. That rectangle should be the location of the text + * component's caret, in screen coordinates. + * + * @param r The text component's caret position, in screen coordinates. + */ + public void setLocationRelativeTo(Rectangle r) { + + // Multi-monitor support - make sure the completion window (and + // description window, if applicable) both fit in the same window in + // a multi-monitor environment. To do this, we decide which monitor + // the rectangle "r" is in, and use that one (just pick top-left corner + // as the defining point). + Rectangle screenBounds = Util.getScreenBoundsForPoint(r.x, r.y); + //Dimension screenSize = getToolkit().getScreenSize(); + + boolean showDescWindow = descWindow != null && ac.isShowDescWindow(); + int totalH = getHeight(); + if (showDescWindow) { + totalH = Math.max(totalH, descWindow.getHeight()); + } + + // Try putting our stuff "below" the caret first. We assume that the + // entire height of our stuff fits on the screen one way or the other. + aboveCaret = false; + int y = r.y + r.height + VERTICAL_SPACE; + if (y + totalH > screenBounds.height) { + y = r.y - VERTICAL_SPACE - getHeight(); + aboveCaret = true; + } + + // Get x-coordinate of completions. Try to align left edge with the + // caret first. + int x = r.x; + if (!ac.getTextComponentOrientation().isLeftToRight()) { + x -= getWidth(); // RTL => align right edge + } + if (x < screenBounds.x) { + x = screenBounds.x; + } else if (x + getWidth() > screenBounds.x + screenBounds.width) { // completions don't fit + x = screenBounds.x + screenBounds.width - getWidth(); + } + + setLocation(x, y); + + // Position the description window, if necessary. + if (showDescWindow) { + positionDescWindow(); + } + + } + + + /** + * Toggles the visibility of this popup window. + * + * @param visible Whether this window should be visible. + */ + @Override + public void setVisible(boolean visible) { + + if (visible != isVisible()) { + + if (visible) { + installKeyBindings(); + lastLine = ac.getLineOfCaret(); + selectFirstItem(); + if (descWindow == null && ac.isShowDescWindow()) { + descWindow = createDescriptionWindow(); + positionDescWindow(); + } + // descWindow needs a kick-start the first time it's displayed. + // Also, the newly-selected item in the choices list is + // probably different from the previous one anyway. + if (descWindow != null) { + Completion c = (Completion) list.getSelectedValue(); + if (c != null) { + descWindow.setDescriptionFor(c); + } + } + } else { + uninstallKeyBindings(); + } + + super.setVisible(visible); + + // Some languages, such as Java, can use quite a lot of memory + // when displaying hundreds of completion choices. We pro-actively + // clear our list model here to make them available for GC. + // Otherwise, they stick around, and consider the following: a + // user starts code-completion for Java 5 SDK classes, then hides + // the dialog, then changes the "class path" to use a Java 6 SDK + // instead. On pressing Ctrl+space, a new array of Completions is + // created. If this window holds on to the previous Completions, + // you're getting roughly 2x the necessary Completions in memory + // until the Completions are actually passed to this window. + if (!visible) { // Do after super.setVisible(false) + lastSelection = (Completion) list.getSelectedValue(); + model.clear(); + } + + // Must set descWindow's visibility one way or the other each time, + // because of the way child JWindows' visibility is handled - in + // some ways it's dependent on the parent, in other ways it's not. + if (descWindow != null) { + descWindow.setVisible(visible && ac.isShowDescWindow()); + } + + } + + } + + + /** + * Stops intercepting certain keystrokes from the text component. + * + * @see #installKeyBindings() + */ + private void uninstallKeyBindings() { + + if (AutoCompletion.isDebug()) { + FineLoggerFactory.getLogger().debug("PopupWindow: Removing keybindings"); + } + + JTextComponent comp = ac.getTextComponent(); + InputMap im = comp.getInputMap(); + ActionMap am = comp.getActionMap(); + + putBackAction(im, am, KeyEvent.VK_ESCAPE, oldEscape); + putBackAction(im, am, KeyEvent.VK_UP, oldUp); + putBackAction(im, am, KeyEvent.VK_DOWN, oldDown); + putBackAction(im, am, KeyEvent.VK_LEFT, oldLeft); + putBackAction(im, am, KeyEvent.VK_RIGHT, oldRight); + putBackAction(im, am, KeyEvent.VK_ENTER, oldEnter); + putBackAction(im, am, KeyEvent.VK_TAB, oldTab); + putBackAction(im, am, KeyEvent.VK_HOME, oldHome); + putBackAction(im, am, KeyEvent.VK_END, oldEnd); + putBackAction(im, am, KeyEvent.VK_PAGE_UP, oldPageUp); + putBackAction(im, am, KeyEvent.VK_PAGE_DOWN, oldPageDown); + + // Ctrl+C + KeyStroke ks = getCopyKeyStroke(); + am.put(im.get(ks), oldCtrlC.action); // Original action + im.put(ks, oldCtrlC.key); // Original key + + comp.removeCaretListener(this); + + } + + + /** + * Updates the LookAndFeel of this window and the description + * window. + */ + public void updateUI() { + SwingUtilities.updateComponentTreeUI(this); + if (descWindow != null) { + descWindow.updateUI(); + } + } + + + /** + * Called when a new item is selected in the popup list. + * + * @param e The event. + */ + public void valueChanged(ListSelectionEvent e) { + if (!e.getValueIsAdjusting()) { + Object value = list.getSelectedValue(); + if (value != null && descWindow != null) { + descWindow.setDescriptionFor((Completion) value); + positionDescWindow(); + } + } + } + + + class CopyAction extends AbstractAction { + + public void actionPerformed(ActionEvent e) { + boolean doNormalCopy = false; + if (descWindow != null && descWindow.isVisible()) { + doNormalCopy = !descWindow.copy(); + } + if (doNormalCopy) { + ac.getTextComponent().copy(); + } + } + + } + + + class DownAction extends AbstractAction { + + public void actionPerformed(ActionEvent e) { + if (isVisible()) { + selectNextItem(); + } + } + + } + + + class EndAction extends AbstractAction { + + public void actionPerformed(ActionEvent e) { + if (isVisible()) { + selectLastItem(); + } + } + + } + + + class EnterAction extends AbstractAction { + + public void actionPerformed(ActionEvent e) { + if (isVisible()) { + insertSelectedCompletion(); + refreshInstallComp(); + } + } + + } + + + class EscapeAction extends AbstractAction { + + public void actionPerformed(ActionEvent e) { + if (isVisible()) { + setVisible(false); + } + } + + } + + + class HomeAction extends AbstractAction { + + public void actionPerformed(ActionEvent e) { + if (isVisible()) { + selectFirstItem(); + } + } + + } + + + /** + * A mapping from a key (an Object) to an Action. + */ + private static class KeyActionPair { + + public Object key; + public Action action; + + public KeyActionPair() { + } + + public KeyActionPair(Object key, Action a) { + this.key = key; + this.action = a; + } + + } + + + class LeftAction extends AbstractAction { + + public void actionPerformed(ActionEvent e) { + if (isVisible()) { + JTextComponent comp = ac.getTextComponent(); + Caret c = comp.getCaret(); + int dot = c.getDot(); + if (dot > 0) { + c.setDot(--dot); + // Ensure moving left hasn't moved us up a line, thus + // hiding the popup window. + if (comp.isVisible()) { + if (lastLine != -1) { + doAutocomplete(); + } + } + } + } + } + + } + + + class PageDownAction extends AbstractAction { + + public void actionPerformed(ActionEvent e) { + if (isVisible()) { + selectPageDownItem(); + } + } + + } + + + class PageUpAction extends AbstractAction { + + public void actionPerformed(ActionEvent e) { + if (isVisible()) { + selectPageUpItem(); + } + } + + } + + + /** + * The actual list of completion choices in this popup window. + */ + private class PopupList extends JList { + + public PopupList(CompletionListModel model) { + super(model); + } + + @Override + public void setUI(ListUI ui) { + if (Util.getUseSubstanceRenderers() && + SUBSTANCE_LIST_UI.equals(ui.getClass().getName())) { + // Substance requires its special ListUI be installed for + // its renderers to actually render (!), but long completion + // lists (e.g. PHPCompletionProvider in RSTALanguageSupport) + // will simply populate too slowly on initial display (when + // calculating preferred size of all items), so in this case + // we give a prototype cell value. + CompletionProvider p = ac.getCompletionProvider(); + BasicCompletion bc = new BasicCompletion(p, "Hello world"); + setPrototypeCellValue(bc); + } else { + // Our custom UI that is faster for long HTML completion lists. + ui = new FastListUI(); + setPrototypeCellValue(null); + } + super.setUI(ui); + } + + } + + + class RightAction extends AbstractAction { + + public void actionPerformed(ActionEvent e) { + if (isVisible()) { + JTextComponent comp = ac.getTextComponent(); + Caret c = comp.getCaret(); + int dot = c.getDot(); + if (dot < comp.getDocument().getLength()) { + c.setDot(++dot); + // Ensure moving right hasn't moved us up a line, thus + // hiding the popup window. + if (comp.isVisible()) { + if (lastLine != -1) { + doAutocomplete(); + } + } + } + } + } + + } + + + class UpAction extends AbstractAction { + + public void actionPerformed(ActionEvent e) { + if (isVisible()) { + selectPreviousItem(); + } + } + + } + + +} \ No newline at end of file diff --git a/designer-base/src/main/java/com/fr/design/gui/autocomplete/FormulaCompletion.java b/designer-base/src/main/java/com/fr/design/gui/autocomplete/FormulaCompletion.java new file mode 100644 index 000000000..3b344dc35 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/gui/autocomplete/FormulaCompletion.java @@ -0,0 +1,22 @@ +package com.fr.design.gui.autocomplete; + +import javax.swing.Icon; + +/** + * @author Hoky + * @date 2021/11/5 + */ +public class FormulaCompletion extends BasicCompletion { + private Icon icon; + + public FormulaCompletion(CompletionProvider provider, String replacementText, Icon icon) { + super(provider, replacementText); + this.icon = icon; + } + + @Override + public Icon getIcon() { + return icon; + } + +} diff --git a/designer-base/src/main/java/com/fr/design/gui/autocomplete/FormulaPaneAutoCompletion.java b/designer-base/src/main/java/com/fr/design/gui/autocomplete/FormulaPaneAutoCompletion.java new file mode 100644 index 000000000..8168c767d --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/gui/autocomplete/FormulaPaneAutoCompletion.java @@ -0,0 +1,1311 @@ +package com.fr.design.gui.autocomplete; + +import com.fr.design.formula.FormulaPane; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.ActionMap; +import javax.swing.InputMap; +import javax.swing.JComponent; +import javax.swing.KeyStroke; +import javax.swing.ListCellRenderer; +import javax.swing.SwingUtilities; +import javax.swing.Timer; +import javax.swing.UIManager; +import javax.swing.event.CaretEvent; +import javax.swing.event.CaretListener; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.text.BadLocationException; +import javax.swing.text.Caret; +import javax.swing.text.Document; +import javax.swing.text.Element; +import javax.swing.text.JTextComponent; +import java.awt.ComponentOrientation; +import java.awt.Dimension; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; +import java.awt.event.HierarchyEvent; +import java.awt.event.HierarchyListener; +import java.awt.event.KeyEvent; +import java.awt.event.WindowEvent; +import java.awt.event.WindowFocusListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.List; + +import static com.fr.design.gui.syntax.ui.rtextarea.RTADefaultInputMap.DEFAULT_MODIFIER; + +/** + * @author Hoky + * @date 2021/11/2 + * @description 重写一个支持刷新公式树组件的AutoCompletion + */ +public class FormulaPaneAutoCompletion extends AutoCompletion { + private FormulaPane.VariableTreeAndDescriptionArea area; + + /** + * The text component we're providing completion for. + */ + private JTextComponent textComponent; + + /** + * The parent window of {@link #textComponent}. + */ + private Window parentWindow; + + /** + * The popup window containing completion choices. + */ + private FormulaAutoCompletePopupWindow popupWindow; + + /** + * The preferred size of the completion choices window. This field exists + * because the user will likely set the preferred size of the window + * before it is actually created. + */ + private Dimension preferredChoicesWindowSize; + + /** + * The preferred size of the optional description window. This field + * only exists because the user may (and usually will) set the size of + * the description window before it exists (it must be parented to a + * Window). + */ + private Dimension preferredDescWindowSize; + + /** + * Manages any parameterized completions that are inserted. + */ + private ParameterizedCompletionContext pcc; + + /** + * Provides the completion options relevant to the current caret position. + */ + private CompletionProvider provider; + + /** + * The renderer to use for the completion choices. If this is + * null, then a default renderer is used. + */ + private ListCellRenderer renderer; + + /** + * The handler to use when an external URL is clicked in the help + * documentation. + */ + private ExternalURLHandler externalURLHandler; + + /** + * An optional redirector that converts URL's to some other location before + * being handed over to externalURLHandler. + */ + private static LinkRedirector linkRedirector; + + /** + * Whether the description window should be displayed along with the + * completion choice window. + */ + private boolean showDescWindow; + + /** + * Whether auto-complete is enabled. + */ + private boolean autoCompleteEnabled; + + /** + * Whether the auto-activation of auto-complete (after a delay, after the + * user types an appropriate character) is enabled. + */ + private boolean autoActivationEnabled; + + /** + * Whether or not, when there is only a single auto-complete option + * that matches the text at the current text position, that text should + * be auto-inserted, instead of the completion window displaying. + */ + private boolean autoCompleteSingleChoices; + + /** + * Whether parameter assistance is enabled. + */ + private boolean parameterAssistanceEnabled; + + /** + * A renderer used for {@link Completion}s in the optional parameter + * choices popup window (displayed when a {@link ParameterizedCompletion} + * is code-completed). If this isn't set, a default renderer is used. + */ + private ListCellRenderer paramChoicesRenderer; + + /** + * The keystroke that triggers the completion window. + */ + private KeyStroke trigger; + + /** + * The previous key in the text component's InputMap for the + * trigger key. + */ + private Object oldTriggerKey; + + /** + * The action previously assigned to {@link #trigger}, so we can reset it + * if the user disables auto-completion. + */ + private Action oldTriggerAction; + + /** + * The previous key in the text component's InputMap for the + * parameter completion trigger key. + */ + private Object oldParenKey; + + /** + * The action previously assigned to the parameter completion key, so we + * can reset it when we uninstall. + */ + private Action oldParenAction; + + /** + * Listens for events in the parent window that affect the visibility of + * the popup windows. + */ + private ParentWindowListener parentWindowListener; + + /** + * Listens for events from the text component that affect the visibility + * of the popup windows. + */ + private TextComponentListener textComponentListener; + + /** + * Listens for events in the text component that cause the popup windows + * to automatically activate. + */ + private AutoActivationListener autoActivationListener; + + /** + * Listens for LAF changes so the auto-complete windows automatically + * update themselves accordingly. + */ + private LookAndFeelChangeListener lafListener; + + /** + * The key used in the input map for the AutoComplete action. + */ + private static final String PARAM_TRIGGER_KEY = "AutoComplete"; + + /** + * Key used in the input map for the parameter completion action. + */ + private static final String PARAM_COMPLETE_KEY = "AutoCompletion.FunctionStart"; + + /** + * Stores how to render auto-completion-specific highlights in text + * components. + */ + private static final AutoCompletionStyleContext STYLE_CONTEXT = + new AutoCompletionStyleContext(); + + /** + * Whether debug messages should be printed to stdout as AutoCompletion + * runs. + */ + private static final boolean DEBUG = initDebug(); + + + /** + * Constructor. + * + * @param provider The completion provider. This cannot be + * null. + */ + public FormulaPaneAutoCompletion(CompletionProvider provider) { + super(provider); + setChoicesWindowSize(350, 200); + setDescriptionWindowSize(350, 250); + + setCompletionProvider(provider); + setTriggerKey(getDefaultTriggerKey()); + setAutoCompleteEnabled(true); + setAutoCompleteSingleChoices(true); + setAutoActivationEnabled(false); + setShowDescWindow(false); + parentWindowListener = new ParentWindowListener(); + textComponentListener = new TextComponentListener(); + autoActivationListener = new AutoActivationListener(); + lafListener = new LookAndFeelChangeListener(); + } + + + /** + * Displays the popup window. Hosting applications can call this method + * to programmatically begin an auto-completion operation. + */ + public void doCompletion() { + refreshPopupWindow(); + } + + + /** + * Returns the delay between when the user types a character and when the + * code completion popup should automatically appear (if applicable). + * + * @return The delay, in milliseconds. + * @see #setAutoActivationDelay(int) + */ + public int getAutoActivationDelay() { + return autoActivationListener.timer.getDelay(); + } + + + /** + * Returns whether, if a single auto-complete choice is available, it + * should be automatically inserted, without displaying the popup menu. + * + * @return Whether to auto-complete single choices. + * @see #setAutoCompleteSingleChoices(boolean) + */ + public boolean isAutoCompleteSingleChoices() { + return autoCompleteSingleChoices; + } + + + /** + * Returns the completion provider. + * + * @return The completion provider. + */ + public CompletionProvider getCompletionProvider() { + return provider; + } + + + /** + * Returns whether debug is enabled for AutoCompletion. + * + * @return Whether debug is enabled. + */ + static boolean isDebug() { + return DEBUG; + } + + + /** + * Returns the default auto-complete "trigger key" for this OS. For + * Windows, for example, it is Ctrl+Space. + * + * @return The default auto-complete trigger key. + */ + public static KeyStroke getDefaultTriggerKey() { + // Default to CTRL, even on Mac, since Ctrl+Space activates Spotlight + return KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, DEFAULT_MODIFIER); + } + + + /** + * Returns the handler to use when an external URL is clicked in the + * description window. + * + * @return The handler. + * @see #setExternalURLHandler(ExternalURLHandler) + * @see #getLinkRedirector() + */ + public ExternalURLHandler getExternalURLHandler() { + return externalURLHandler; + } + + + int getLineOfCaret() { + Document doc = textComponent.getDocument(); + Element root = doc.getDefaultRootElement(); + return root.getElementIndex(textComponent.getCaretPosition()); + } + + + /** + * Returns the link redirector, if any. + * + * @return The link redirector, or null if none. + * @see #setLinkRedirector(LinkRedirector) + */ + public static LinkRedirector getLinkRedirector() { + return linkRedirector; + } + + + /** + * Returns the default list cell renderer used when a completion provider + * does not supply its own. + * + * @return The default list cell renderer. + * @see #setListCellRenderer(ListCellRenderer) + */ + public ListCellRenderer getListCellRenderer() { + return renderer; + } + + + /** + * Returns the renderer to use for {@link Completion}s in the optional + * parameter choices popup window (displayed when a + * {@link ParameterizedCompletion} is code-completed). If this returns + * null, a default renderer is used. + * + * @return The renderer to use. + * @see #setParamChoicesRenderer(ListCellRenderer) + * @see #isParameterAssistanceEnabled() + */ + public ListCellRenderer getParamChoicesRenderer() { + return paramChoicesRenderer; + } + + + /** + * Returns the text to replace with in the document. This is a + * "last-chance" hook for subclasses to make special modifications to the + * completion text inserted. The default implementation simply returns + * c.getReplacementText(). You usually will not need to modify + * this method. + * + * @param c The completion being inserted. + * @param doc The document being modified. + * @param start The start of the text being replaced. + * @param len The length of the text being replaced. + * @return The text to replace with. + */ + protected String getReplacementText(Completion c, Document doc, int start, + int len) { + return c.getReplacementText(); + } + + + /** + * Returns whether the "description window" should be shown alongside + * the completion window. + * + * @return Whether the description window should be shown. + * @see #setShowDescWindow(boolean) + */ + public boolean isShowDescWindow() { + return showDescWindow; + } + + + /** + * Returns the style context describing how auto-completion related + * highlights in the editor are rendered. + * + * @return The style context. + */ + public static AutoCompletionStyleContext getStyleContext() { + return STYLE_CONTEXT; + } + + + /** + * Returns the text component for which auto-completion is enabled. + * + * @return The text component, or null if this + * {@link AutoCompletion} is not installed on any text component. + * @see #install(JTextComponent) + */ + public JTextComponent getTextComponent() { + return textComponent; + } + + + /** + * Returns the orientation of the text component we're installed to. + * + * @return The orientation of the text component, or null if + * we are not installed on one. + */ + ComponentOrientation getTextComponentOrientation() { + return textComponent == null ? null : + textComponent.getComponentOrientation(); + } + + + /** + * Returns the "trigger key" used for auto-complete. + * + * @return The trigger key. + * @see #setTriggerKey(KeyStroke) + */ + public KeyStroke getTriggerKey() { + return trigger; + } + + + /** + * Hides any child windows being displayed by the auto-completion system. + * + * @return Whether any windows were visible. + */ + public boolean hideChildWindows() { + //return hidePopupWindow() || hideToolTipWindow(); + boolean res = hidePopupWindow(); + res |= hideParameterCompletionPopups(); + return res; + } + + + /** + * Hides and disposes of any parameter completion-related popups. + * + * @return Whether any such windows were visible (and thus hidden). + */ + private boolean hideParameterCompletionPopups() { + if (pcc != null) { + pcc.deactivate(); + pcc = null; + return true; + } + return false; + } + + + /** + * Hides the popup window, if it is visible. + * + * @return Whether the popup window was visible. + */ + private boolean hidePopupWindow() { + if (popupWindow != null) { + if (popupWindow.isVisible()) { + popupWindow.setVisible(false); + return true; + } + } + return false; + } + + + /** + * Determines whether debug should be enabled for the AutoCompletion + * library. This method checks a system property, but takes care of + * {@link SecurityException}s in case we're in an applet or WebStart. + * + * @return Whether debug should be enabled. + */ + private static boolean initDebug() { + boolean debug = false; + try { + debug = Boolean.getBoolean("AutoCompletion.debug"); + } catch (SecurityException se) { // We're in an applet or WebStart. + debug = false; + } + return debug; + } + + + /** + * Installs this auto-completion on a text component. If this + * {@link AutoCompletion} is already installed on another text component, + * it is uninstalled first. + * + * @param c The text component. + * @see #uninstall() + */ + public void install(JTextComponent c) { + + if (textComponent != null) { + uninstall(); + } + + this.textComponent = c; + installTriggerKey(getTriggerKey()); + + // Install the function completion key, if there is one. + // NOTE: We cannot do this if the start char is ' ' (e.g. just a space + // between the function name and parameters) because it overrides + // RSTA's special space action. It seems KeyStorke.getKeyStroke(' ') + // hoses ctrl+space, shift+space, etc., even though I think it + // shouldn't... + char start = provider.getParameterListStart(); + if (start != 0 && start != ' ') { + InputMap im = c.getInputMap(); + ActionMap am = c.getActionMap(); + KeyStroke ks = KeyStroke.getKeyStroke(start); + oldParenKey = im.get(ks); + im.put(ks, PARAM_COMPLETE_KEY); + oldParenAction = am.get(PARAM_COMPLETE_KEY); + am.put(PARAM_COMPLETE_KEY, + new ParameterizedCompletionStartAction(start)); + } + + textComponentListener.addTo(this.textComponent); + // In case textComponent is already in a window... + textComponentListener.hierarchyChanged(null); + + if (isAutoActivationEnabled()) { + autoActivationListener.addTo(this.textComponent); + } + + UIManager.addPropertyChangeListener(lafListener); + updateUI(); // In case there have been changes since we uninstalled + + } + + + /** + * Installs a "trigger key" action onto the current text component. + * + * @param ks The keystroke that should trigger the action. + */ + private void installTriggerKey(KeyStroke ks) { + InputMap im = textComponent.getInputMap(); + oldTriggerKey = im.get(ks); + im.put(ks, PARAM_TRIGGER_KEY); + ActionMap am = textComponent.getActionMap(); + oldTriggerAction = am.get(PARAM_TRIGGER_KEY); + am.put(PARAM_TRIGGER_KEY, new AutoCompleteAction()); + } + + + /** + * Returns whether auto-activation is enabled (that is, whether the + * completion popup will automatically appear after a delay when the user + * types an appropriate character). Note that this parameter will be + * ignored if auto-completion is disabled. + * + * @return Whether auto-activation is enabled. + * @see #setAutoActivationEnabled(boolean) + * @see #getAutoActivationDelay() + * @see #isAutoCompleteEnabled() + */ + public boolean isAutoActivationEnabled() { + return autoActivationEnabled; + } + + + /** + * Returns whether auto-completion is enabled. + * + * @return Whether auto-completion is enabled. + * @see #setAutoCompleteEnabled(boolean) + */ + public boolean isAutoCompleteEnabled() { + return autoCompleteEnabled; + } + + + /** + * Returns whether parameter assistance is enabled. + * + * @return Whether parameter assistance is enabled. + * @see #setParameterAssistanceEnabled(boolean) + */ + public boolean isParameterAssistanceEnabled() { + return parameterAssistanceEnabled; + } + + + /** + * Returns whether the completion popup window is visible. + * + * @return Whether the completion popup window is visible. + */ + public boolean isPopupVisible() { + return popupWindow != null && popupWindow.isVisible(); + } + + private boolean needSetPopupWindow(int count, int textLen) { + return (count == 1 && (isPopupVisible() || textLen == 0)) + || (count == 1 && !isAutoCompleteSingleChoices()) + || count > 1; + } + + private FormulaAutoCompletePopupWindow createAutoCompletePopupWindow() { + FormulaAutoCompletePopupWindow popupWindow = new FormulaAutoCompletePopupWindow(parentWindow, this); + // Completion is usually done for code, which is always done + // LTR, so make completion stuff RTL only if text component is + // also RTL. + popupWindow.applyComponentOrientation( + getTextComponentOrientation()); + if (renderer != null) { + popupWindow.setListCellRenderer(renderer); + } + if (preferredChoicesWindowSize != null) { + popupWindow.setSize(preferredChoicesWindowSize); + } + if (preferredDescWindowSize != null) { + popupWindow.setDescriptionWindowSize( + preferredDescWindowSize); + } + return popupWindow; + } + + /** + * Sets the delay between when the user types a character and when the + * code completion popup should automatically appear (if applicable). + * + * @param ms The delay, in milliseconds. This should be greater than zero. + * @see #getAutoActivationDelay() + */ + public void setAutoActivationDelay(int ms) { + ms = Math.max(0, ms); + autoActivationListener.timer.stop(); + autoActivationListener.timer.setInitialDelay(ms); + } + + + /** + * Toggles whether auto-activation is enabled. Note that auto-activation + * also depends on auto-completion itself being enabled. + * + * @param enabled Whether auto-activation is enabled. + * @see #isAutoActivationEnabled() + * @see #setAutoActivationDelay(int) + */ + public void setAutoActivationEnabled(boolean enabled) { + if (enabled != autoActivationEnabled) { + autoActivationEnabled = enabled; + if (textComponent != null) { + if (autoActivationEnabled) { + autoActivationListener.addTo(textComponent); + } else { + autoActivationListener.removeFrom(textComponent); + } + } + } + } + + + /** + * Sets whether auto-completion is enabled. + * + * @param enabled Whether auto-completion is enabled. + * @see #isAutoCompleteEnabled() + */ + public void setAutoCompleteEnabled(boolean enabled) { + if (enabled != autoCompleteEnabled) { + autoCompleteEnabled = enabled; + hidePopupWindow(); + } + } + + + /** + * Sets whether, if a single auto-complete choice is available, it should + * be automatically inserted, without displaying the popup menu. + * + * @param autoComplete Whether to auto-complete single choices. + * @see #isAutoCompleteSingleChoices() + */ + public void setAutoCompleteSingleChoices(boolean autoComplete) { + autoCompleteSingleChoices = autoComplete; + } + + + /** + * Sets the completion provider being used. + * + * @param provider The new completion provider. This cannot be + * null. + * @throws IllegalArgumentException If provider is + * null. + */ + public void setCompletionProvider(CompletionProvider provider) { + if (provider == null) { + throw new IllegalArgumentException("provider cannot be null"); + } + this.provider = provider; + hidePopupWindow(); // In case new choices should be displayed. + } + + + /** + * Sets the size of the completion choices window. + * + * @param w The new width. + * @param h The new height. + * @see #setDescriptionWindowSize(int, int) + */ + public void setChoicesWindowSize(int w, int h) { + preferredChoicesWindowSize = new Dimension(w, h); + if (popupWindow != null) { + popupWindow.setSize(preferredChoicesWindowSize); + } + } + + + /** + * Sets the size of the description window. + * + * @param w The new width. + * @param h The new height. + * @see #setChoicesWindowSize(int, int) + */ + public void setDescriptionWindowSize(int w, int h) { + preferredDescWindowSize = new Dimension(w, h); + if (popupWindow != null) { + popupWindow.setDescriptionWindowSize(preferredDescWindowSize); + } + } + + + /** + * Sets the handler to use when an external URL is clicked in the + * description window. This handler can perform some action, such as + * open the URL in a web browser. The default implementation will open + * the URL in a browser, but only if running in Java 6. If you want + * browser support for Java 5 and below, or otherwise want to respond to + * hyperlink clicks, you will have to install your own handler to do so. + * + * @param handler The new handler. + * @see #getExternalURLHandler() + */ + public void setExternalURLHandler(ExternalURLHandler handler) { + this.externalURLHandler = handler; + } + + + /** + * Sets the redirector for external URL's found in code completion + * documentation. When a non-local link in completion popups is clicked, + * this redirector is given the chance to modify the URL fetched and + * displayed. + * + * @param redirector The link redirector, or null for + * none. + * @see #getLinkRedirector() + */ + public static void setLinkRedirector(LinkRedirector redirector) { + linkRedirector = redirector; + } + + + /** + * Sets the default list cell renderer to use when a completion provider + * does not supply its own. + * + * @param renderer The renderer to use. If this is null, + * a default renderer is used. + * @see #getListCellRenderer() + */ + public void setListCellRenderer(ListCellRenderer renderer) { + this.renderer = renderer; + if (popupWindow != null) { + popupWindow.setListCellRenderer(renderer); + hidePopupWindow(); + } + } + + + /** + * Sets the renderer to use for {@link Completion}s in the optional + * parameter choices popup window (displayed when a + * {@link ParameterizedCompletion} is code-completed). If this isn't set, + * a default renderer is used. + * + * @param r The renderer to use. + * @see #getParamChoicesRenderer() + * @see #setParameterAssistanceEnabled(boolean) + */ + public void setParamChoicesRenderer(ListCellRenderer r) { + paramChoicesRenderer = r; + } + + + /** + * Sets whether parameter assistance is enabled. If parameter assistance + * is enabled, and a "parameterized" completion (such as a function or + * method) is inserted, the user will get "assistance" in inserting the + * parameters in the form of a popup window with documentation and easy + * tabbing through the arguments (as seen in Eclipse and NetBeans). + * + * @param enabled Whether parameter assistance should be enabled. + * @see #isParameterAssistanceEnabled() + */ + public void setParameterAssistanceEnabled(boolean enabled) { + parameterAssistanceEnabled = enabled; + } + + + /** + * Sets whether the "description window" should be shown beside the + * completion window. + * + * @param show Whether to show the description window. + * @see #isShowDescWindow() + */ + public void setShowDescWindow(boolean show) { + hidePopupWindow(); // Needed to force it to take effect + showDescWindow = show; + } + + + /** + * Sets the keystroke that should be used to trigger the auto-complete + * popup window. + * + * @param ks The keystroke. + * @throws IllegalArgumentException If ks is null. + * @see #getTriggerKey() + */ + public void setTriggerKey(KeyStroke ks) { + if (ks == null) { + throw new IllegalArgumentException("trigger key cannot be null"); + } + if (!ks.equals(trigger)) { + if (textComponent != null) { + // Put old trigger action back. + uninstallTriggerKey(); + // Grab current action for new trigger and replace it. + installTriggerKey(ks); + } + trigger = ks; + } + } + + + /** + * Uninstalls this auto-completion from its text component. If it is not + * installed on any text component, nothing happens. + * + * @see #install(JTextComponent) + */ + public void uninstall() { + + if (textComponent != null) { + + hidePopupWindow(); // Unregisters listeners, actions, etc. + + uninstallTriggerKey(); + + // Uninstall the function completion key. + char start = provider.getParameterListStart(); + if (start != 0) { + KeyStroke ks = KeyStroke.getKeyStroke(start); + InputMap im = textComponent.getInputMap(); + im.put(ks, oldParenKey); + ActionMap am = textComponent.getActionMap(); + am.put(PARAM_COMPLETE_KEY, oldParenAction); + } + + textComponentListener.removeFrom(textComponent); + if (parentWindow != null) { + parentWindowListener.removeFrom(parentWindow); + } + + if (isAutoActivationEnabled()) { + autoActivationListener.removeFrom(textComponent); + } + + UIManager.removePropertyChangeListener(lafListener); + + textComponent = null; + popupWindow = null; + + } + + } + + + /** + * Replaces the "trigger key" action with the one that was there + * before auto-completion was installed. + */ + private void uninstallTriggerKey() { + InputMap im = textComponent.getInputMap(); + im.put(trigger, oldTriggerKey); + ActionMap am = textComponent.getActionMap(); + am.put(PARAM_TRIGGER_KEY, oldTriggerAction); + } + + + /** + * Updates the LookAndFeel of the popup window. Applications can call + * this method as appropriate if they support changing the LookAndFeel + * at runtime. + */ + private void updateUI() { + if (popupWindow != null) { + popupWindow.updateUI(); + } + if (pcc != null) { + pcc.updateUI(); + } + // Will practically always be a JComponent (a JLabel) + if (paramChoicesRenderer instanceof JComponent) { + ((JComponent) paramChoicesRenderer).updateUI(); + } + } + + + /** + * Listens for events in the text component to auto-activate the code + * completion popup. + */ + private class AutoActivationListener extends FocusAdapter + implements DocumentListener, CaretListener, ActionListener { + + private Timer timer; + private boolean justInserted; + + public AutoActivationListener() { + timer = new Timer(200, this); + timer.setRepeats(false); + } + + public void actionPerformed(ActionEvent e) { + doCompletion(); + } + + public void addTo(JTextComponent tc) { + tc.addFocusListener(this); + tc.getDocument().addDocumentListener(this); + tc.addCaretListener(this); + } + + public void caretUpdate(CaretEvent e) { + if (justInserted) { + justInserted = false; + } else { + timer.stop(); + } + } + + public void changedUpdate(DocumentEvent e) { + // Ignore + } + + @Override + public void focusLost(FocusEvent e) { + timer.stop(); + //hideChildWindows(); Other listener will do this + } + + public void insertUpdate(DocumentEvent e) { + justInserted = false; + if (isAutoCompleteEnabled() && isAutoActivationEnabled() && + e.getLength() == 1) { + if (provider.isAutoActivateOkay(textComponent)) { + timer.restart(); + justInserted = true; + } else { + timer.stop(); + } + } else { + timer.stop(); + } + } + + public void removeFrom(JTextComponent tc) { + tc.removeFocusListener(this); + tc.getDocument().removeDocumentListener(this); + tc.removeCaretListener(this); + timer.stop(); + justInserted = false; + } + + public void removeUpdate(DocumentEvent e) { + timer.stop(); + } + + } + + + /** + * The Action that displays the popup window if + * auto-completion is enabled. + */ + private class AutoCompleteAction extends AbstractAction { + + public void actionPerformed(ActionEvent e) { + if (isAutoCompleteEnabled()) { + refreshPopupWindow(); + } else if (oldTriggerAction != null) { + oldTriggerAction.actionPerformed(e); + } + } + + } + + + /** + * Listens for LookAndFeel changes and updates the various popup windows + * involved in auto-completion accordingly. + */ + private class LookAndFeelChangeListener implements PropertyChangeListener { + + public void propertyChange(PropertyChangeEvent e) { + String name = e.getPropertyName(); + if ("lookAndFeel".equals(name)) { + updateUI(); + } + } + + } + + + /** + * Action that starts a parameterized completion, e.g. after '(' is + * typed. + */ + private class ParameterizedCompletionStartAction extends AbstractAction { + + private String start; + + public ParameterizedCompletionStartAction(char ch) { + this.start = Character.toString(ch); + } + + public void actionPerformed(ActionEvent e) { + + // Prevents keystrokes from messing up + boolean wasVisible = hidePopupWindow(); + + // Only proceed if they were selecting a completion + if (!wasVisible || !isParameterAssistanceEnabled()) { + textComponent.replaceSelection(start); + return; + } + + Completion c = popupWindow.getSelection(); + if (c instanceof ParameterizedCompletion) { // Should always be true + // Fixes capitalization of the entered text. + insertCompletion(c, true); + } + + } + + } + + + /** + * Listens for events in the parent window of the text component with + * auto-completion enabled. + */ + private class ParentWindowListener extends ComponentAdapter + implements WindowFocusListener { + + public void addTo(Window w) { + w.addComponentListener(this); + w.addWindowFocusListener(this); + } + + @Override + public void componentHidden(ComponentEvent e) { + hideChildWindows(); + } + + @Override + public void componentMoved(ComponentEvent e) { + hideChildWindows(); + } + + @Override + public void componentResized(ComponentEvent e) { + hideChildWindows(); + } + + public void removeFrom(Window w) { + w.removeComponentListener(this); + w.removeWindowFocusListener(this); + } + + public void windowGainedFocus(WindowEvent e) { + } + + public void windowLostFocus(WindowEvent e) { + hideChildWindows(); + } + + } + + + /** + * Listens for events from the text component we're installed on. + */ + private class TextComponentListener extends FocusAdapter + implements HierarchyListener { + + void addTo(JTextComponent tc) { + tc.addFocusListener(this); + tc.addHierarchyListener(this); + } + + /** + * Hide the auto-completion windows when the text component loses + * focus. + */ + @Override + public void focusLost(FocusEvent e) { + hideChildWindows(); + } + + /** + * Called when the component hierarchy for our text component changes. + * When the text component is added to a new {@link Window}, this + * method registers listeners on that Window. + * + * @param e The event. + */ + public void hierarchyChanged(HierarchyEvent e) { + + // NOTE: e many be null as we call this method at other times. + //System.out.println("Hierarchy changed! " + e); + + Window oldParentWindow = parentWindow; + parentWindow = SwingUtilities.getWindowAncestor(textComponent); + if (parentWindow != oldParentWindow) { + if (oldParentWindow != null) { + parentWindowListener.removeFrom(oldParentWindow); + } + if (parentWindow != null) { + parentWindowListener.addTo(parentWindow); + } + } + + } + + public void removeFrom(JTextComponent tc) { + tc.removeFocusListener(this); + tc.removeHierarchyListener(this); + } + + } + + public void installVariableTree(FormulaPane.VariableTreeAndDescriptionArea jComp) { + area = jComp; + } + + /** + * Inserts a completion. Any time a code completion event occurs, the + * actual text insertion happens through this method. + * + * @param c A completion to insert. This cannot be null. + * @param typedParamListStartChar Whether the parameterized completion + * start character was typed (typically '('). + */ + protected void insertCompletion(Completion c, + boolean typedParamListStartChar) { + + JTextComponent textComp = getTextComponent(); + String alreadyEntered = c.getAlreadyEntered(textComp); + hidePopupWindow(); + Caret caret = textComp.getCaret(); + + int dot = caret.getDot(); + int len = alreadyEntered.length(); + int start = dot - len; + String replacement = getReplacementText(c, textComp.getDocument(), + start, len); + + caret.setDot(start); + caret.moveDot(dot); + if (FormulaPane.containsParam(replacement)) { + textComp.replaceSelection(FormulaPane.getParamPrefix(replacement) + replacement); + int caretPosition = textComp.getCaretPosition(); + textComp.setCaretPosition(caretPosition); + } else { + textComp.replaceSelection(replacement + "()"); + int caretPosition = textComp.getCaretPosition(); + textComp.setCaretPosition(caretPosition - 1); + } + + + if (isParameterAssistanceEnabled() && + (c instanceof ParameterizedCompletion)) { + ParameterizedCompletion pc = (ParameterizedCompletion) c; + startParameterizedCompletionAssistance(pc, typedParamListStartChar); + } + + } + + /** + * Displays a "tool tip" detailing the inputs to the function just entered. + * + * @param pc The completion. + * @param typedParamListStartChar Whether the parameterized completion list + * starting character was typed. + */ + private void startParameterizedCompletionAssistance( + ParameterizedCompletion pc, boolean typedParamListStartChar) { + + // Get rid of the previous tool tip window, if there is one. + hideParameterCompletionPopups(); + + // Don't bother with a tool tip if there are no parameters, but if + // they typed e.g. the opening '(', make them overtype the ')'. + if (pc.getParamCount() == 0 && !(pc instanceof TemplateCompletion)) { + CompletionProvider p = pc.getProvider(); + char end = p.getParameterListEnd(); // Might be '\0' + String text = end == '\0' ? "" : Character.toString(end); + if (typedParamListStartChar) { + String template = "${}" + text + "${cursor}"; + textComponent.replaceSelection(Character.toString(p.getParameterListStart())); + TemplateCompletion tc = new TemplateCompletion(p, null, null, template); + pc = tc; + } else { + text = p.getParameterListStart() + text; + textComponent.replaceSelection(text); + return; + } + } + + pcc = new ParameterizedCompletionContext(parentWindow, this, pc); + pcc.activate(); + + } + + protected int refreshPopupWindow() { + // A return value of null => don't suggest completions + String text = provider.getAlreadyEnteredText(textComponent); + if (text == null && !isPopupVisible()) { + return getLineOfCaret(); + } + // If the popup is currently visible, and they type a space (or any + // character that resets the completion list to "all completions"), + // the popup window should be hidden instead of being reset to show + // everything. + int textLen = text == null ? 0 : text.length(); + if (textLen == 0 && isPopupVisible()) { + hidePopupWindow(); + return getLineOfCaret(); + } + final List completions = provider.getCompletions(textComponent); + int count = completions.size(); + if (needSetPopupWindow(count, textLen)) { + if (popupWindow == null) { + popupWindow = createAutoCompletePopupWindow(); + } + popupWindow.installComp(area); + popupWindow.setCompletions(completions); + if (!popupWindow.isVisible()) { + Rectangle r = null; + try { + r = textComponent.modelToView(textComponent.getCaretPosition()); + } catch (BadLocationException ble) { + return -1; + } + Point p = new Point(r.x, r.y); + SwingUtilities.convertPointToScreen(p, textComponent); + r.x = p.x; + r.y = p.y; + popupWindow.setLocationRelativeTo(r); + popupWindow.setVisible(true); + } + } else if (count == 1) { // !isPopupVisible && autoCompleteSingleChoices + SwingUtilities.invokeLater(new Runnable() { + public void run() { + insertCompletion(completions.get(0)); + } + }); + } else { + hidePopupWindow(); + } + return getLineOfCaret(); + } +} diff --git a/designer-base/src/main/java/com/fr/design/gui/autocomplete/ParameterizedCompletionContext.java b/designer-base/src/main/java/com/fr/design/gui/autocomplete/ParameterizedCompletionContext.java index 78d5e74a4..238fa496b 100644 --- a/designer-base/src/main/java/com/fr/design/gui/autocomplete/ParameterizedCompletionContext.java +++ b/designer-base/src/main/java/com/fr/design/gui/autocomplete/ParameterizedCompletionContext.java @@ -16,16 +16,34 @@ import com.fr.design.gui.syntax.ui.rsyntaxtextarea.RSyntaxTextArea; import com.fr.design.gui.syntax.ui.rtextarea.ChangeableHighlightPainter; import com.fr.log.FineLoggerFactory; -import javax.swing.*; +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.ActionMap; +import javax.swing.InputMap; +import javax.swing.KeyStroke; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; import javax.swing.event.CaretEvent; import javax.swing.event.CaretListener; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; -import javax.swing.text.*; +import javax.swing.text.AbstractDocument; +import javax.swing.text.BadLocationException; +import javax.swing.text.DefaultEditorKit; +import javax.swing.text.Document; +import javax.swing.text.Highlighter; import javax.swing.text.Highlighter.Highlight; import javax.swing.text.Highlighter.HighlightPainter; -import java.awt.*; -import java.awt.event.*; +import javax.swing.text.JTextComponent; +import javax.swing.text.Position; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; import java.util.ArrayList; import java.util.List; @@ -41,7 +59,7 @@ import java.util.List; * @author Robert Futrell * @version 1.0 */ -class ParameterizedCompletionContext { +public class ParameterizedCompletionContext { /** * The parent window. diff --git a/designer-base/src/main/resources/com/fr/design/images/m_file/formula.png b/designer-base/src/main/resources/com/fr/design/images/m_file/formula.png new file mode 100644 index 0000000000000000000000000000000000000000..7e495da1b29cd915984ef9552ac59c6e96a33e4c GIT binary patch literal 279 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uuz(rC1}QWNE&K$e8a-VcLo5W3 zPIBZrpuod&RsZJy{SNn2ZXP#=DbWrCZGA_SQwTc=PEfg`vOf8_lM0XfRk)z4*}Q$iB}p~g=t literal 0 HcmV?d00001 diff --git a/designer-base/src/main/resources/com/fr/design/images/m_file/param.png b/designer-base/src/main/resources/com/fr/design/images/m_file/param.png new file mode 100644 index 0000000000000000000000000000000000000000..011c395813e8b95877b1d42af583f73059cdd3bb GIT binary patch literal 383 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uuz(rC1}QWNE&K$eUV6GXhFAzD zCoB*!X!;^8J^PRP@j08*&o_LXoXT(}NUbQx?b_j{GmRS)mofj`FDW50pXp^~`nfq- zi76>ZK4|%gZ`iacM=ynK zuGct{ADc2P<9NaQ_y2!Md4(SwV%H29JpcdvtbYH`vN Bg)RU9 literal 0 HcmV?d00001