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 904cdf00b..da61fab15 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,6 +16,7 @@ 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.AutoCompleteExtraRefreshComponent; import com.fr.design.gui.autocomplete.CompletionCellRenderer; import com.fr.design.gui.autocomplete.CompletionProvider; import com.fr.design.gui.autocomplete.DefaultCompletionProvider; @@ -416,7 +417,7 @@ public class FormulaPane extends BasicPane implements KeyListener, UIFormula { autoCompletion = new FormulaPaneAutoCompletion(provider); autoCompletion.setListCellRenderer(new CompletionCellRenderer()); autoCompletion.install(formulaTextArea); - autoCompletion.installVariableTree(variableTreeAndDescriptionArea); + autoCompletion.installExtraRefreshComponent(variableTreeAndDescriptionArea); } @@ -1036,7 +1037,7 @@ public class FormulaPane extends BasicPane implements KeyListener, UIFormula { } } - public class VariableTreeAndDescriptionArea extends JPanel { + public class VariableTreeAndDescriptionArea extends JPanel implements AutoCompleteExtraRefreshComponent { private JTree variablesTree; private UITextArea descriptionTextArea; @@ -1277,6 +1278,11 @@ public class FormulaPane extends BasicPane implements KeyListener, UIFormula { functionTypeList.setSelectedIndex(0); } + @Override + public void refresh(String replacementText) { + refreshText(replacementText); + } + /* * 查看函数的详细信息 */ diff --git a/designer-base/src/main/java/com/fr/design/gui/autocomplete/AutoCompleteExtraRefreshComponent.java b/designer-base/src/main/java/com/fr/design/gui/autocomplete/AutoCompleteExtraRefreshComponent.java new file mode 100644 index 000000000..ff6b7faef --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/gui/autocomplete/AutoCompleteExtraRefreshComponent.java @@ -0,0 +1,5 @@ +package com.fr.design.gui.autocomplete; + +public interface AutoCompleteExtraRefreshComponent { + void refresh(String replacementText); +} diff --git a/designer-base/src/main/java/com/fr/design/gui/autocomplete/AutoCompleteWithExtraRefreshPopupWindow.java b/designer-base/src/main/java/com/fr/design/gui/autocomplete/AutoCompleteWithExtraRefreshPopupWindow.java new file mode 100644 index 000000000..3f4f19177 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/gui/autocomplete/AutoCompleteWithExtraRefreshPopupWindow.java @@ -0,0 +1,982 @@ +package com.fr.design.gui.autocomplete; + +import com.fr.design.gui.syntax.ui.rsyntaxtextarea.PopupWindowDecorator; +import com.fr.log.FineLoggerFactory; +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; +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.ActionMap; +import javax.swing.InputMap; +import javax.swing.JComponent; +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; + +public abstract class AutoCompleteWithExtraRefreshPopupWindow extends JWindow implements CaretListener, + ListSelectionListener, MouseListener { + private final static int DIS = 5; + /** + * The parent AutoCompletion instance. + */ + private AutoCompletion 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; + + + + /** + * 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"; + + + protected AutoCompleteExtraRefreshComponent component; + + + /** + * Constructor. + * + * @param parent The parent window (hosting the text component). + * @param ac The auto-completion instance. + */ + public AutoCompleteWithExtraRefreshPopupWindow(Window parent, final AutoCompletion 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(AutoCompleteExtraRefreshComponent component){ + this.component = component; + } + + public void refreshInstallComp() { + component.refresh(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 && e.getButton() == 1) { + insertSelectedCompletion(); + } + } + + + public void mouseEntered(MouseEvent e) { + } + + + public void mouseExited(MouseEvent e) { + } + + + public void mousePressed(MouseEvent e) { + refreshInstallComp(); + } + + + 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(); + refreshInstallComp(); + } + } + + } + + + 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(); + refreshInstallComp(); + } + } + + } + +} diff --git a/designer-base/src/main/java/com/fr/design/gui/autocomplete/AutoCompletionWithExtraRefresh.java b/designer-base/src/main/java/com/fr/design/gui/autocomplete/AutoCompletionWithExtraRefresh.java new file mode 100644 index 000000000..4def5370d --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/gui/autocomplete/AutoCompletionWithExtraRefresh.java @@ -0,0 +1,1260 @@ +package com.fr.design.gui.autocomplete; + +import com.fr.design.formula.FormulaPane; +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 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 static com.fr.design.gui.syntax.ui.rtextarea.RTADefaultInputMap.DEFAULT_MODIFIER; + +public abstract class AutoCompletionWithExtraRefresh extends AutoCompletion{ + private AutoCompleteExtraRefreshComponent 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 AutoCompleteWithExtraRefreshPopupWindow 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(); + + Window getParentWindow(){ + return parentWindow; + } + + ListCellRenderer getCellRender(){ + return renderer; + } + + Dimension getPreferredChoicesWindowSize(){ + return preferredChoicesWindowSize; + } + + Dimension getPreferredDescWindowSize(){ + return preferredDescWindowSize; + } + /** + * Constructor. + * + * @param provider The completion provider. This cannot be + * null. + */ + public AutoCompletionWithExtraRefresh(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. + */ + protected 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; + } + + protected abstract AutoCompleteWithExtraRefreshPopupWindow createAutoCompletePopupWindow(); + + /** + * 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 installExtraRefreshComponent(AutoCompleteExtraRefreshComponent jComp) { + area = jComp; + } + + + /** + * 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. + */ + protected 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/FormulaAutoCompletePopupWindow.java b/designer-base/src/main/java/com/fr/design/gui/autocomplete/FormulaAutoCompletePopupWindow.java index 0e1c7c6ac..daed7cd4c 100644 --- 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 @@ -9,41 +9,7 @@ */ 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 @@ -55,949 +21,11 @@ import java.util.List; * @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"; +class FormulaAutoCompletePopupWindow extends AutoCompleteWithExtraRefreshPopupWindow { - /** - * 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 && e.getButton() == 1) { - insertSelectedCompletion(); - } - } - - - public void mouseEntered(MouseEvent e) { - } - - - public void mouseExited(MouseEvent e) { - } - - - public void mousePressed(MouseEvent e) { - refreshInstallComp(); - } - - - 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(); - refreshInstallComp(); - } - } - - } - - - 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(); - } - } - + super(parent,ac); } - - 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(); - refreshInstallComp(); - } - } - - } - - } \ No newline at end of file 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 index 8168c767d..cd37fd9bc 100644 --- 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 @@ -2,226 +2,18 @@ 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(); - - +public class FormulaPaneAutoCompletion extends AutoCompletionWithExtraRefresh { /** * Constructor. * @@ -230,957 +22,29 @@ public class FormulaPaneAutoCompletion extends AutoCompletion { */ 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); + @Override + protected AutoCompleteWithExtraRefreshPopupWindow createAutoCompletePopupWindow() { + FormulaAutoCompletePopupWindow popupWindow = new FormulaAutoCompletePopupWindow(getParentWindow(), 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 (getCellRender() != null) { + popupWindow.setListCellRenderer(getCellRender()); } - if (preferredChoicesWindowSize != null) { - popupWindow.setSize(preferredChoicesWindowSize); + if (getPreferredChoicesWindowSize() != null) { + popupWindow.setSize(getPreferredChoicesWindowSize()); } - if (preferredDescWindowSize != null) { + if (getPreferredDescWindowSize() != null) { popupWindow.setDescriptionWindowSize( - preferredDescWindowSize); + getPreferredDescWindowSize()); } 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. @@ -1223,89 +87,4 @@ public class FormulaPaneAutoCompletion extends AutoCompletion { } } - - /** - * 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/JSAutoCompletePopupWindow.java b/designer-base/src/main/java/com/fr/design/gui/autocomplete/JSAutoCompletePopupWindow.java new file mode 100644 index 000000000..80a26bf96 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/gui/autocomplete/JSAutoCompletePopupWindow.java @@ -0,0 +1,16 @@ +package com.fr.design.gui.autocomplete; + +import java.awt.Window; + +public class JSAutoCompletePopupWindow extends AutoCompleteWithExtraRefreshPopupWindow { + + /** + * Constructor. + * + * @param parent The parent window (hosting the text component). + * @param ac The auto-completion instance. + */ + public JSAutoCompletePopupWindow(Window parent, AutoCompletion ac) { + super(parent, ac); + } +} diff --git a/designer-base/src/main/java/com/fr/design/gui/autocomplete/JSImplPaneAutoCompletion.java b/designer-base/src/main/java/com/fr/design/gui/autocomplete/JSImplPaneAutoCompletion.java new file mode 100644 index 000000000..0c534bf72 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/gui/autocomplete/JSImplPaneAutoCompletion.java @@ -0,0 +1,31 @@ +package com.fr.design.gui.autocomplete; + +public class JSImplPaneAutoCompletion extends AutoCompletionWithExtraRefresh{ + /** + * Constructor. + * + * @param provider The completion provider. This cannot be + * null. + */ + public JSImplPaneAutoCompletion(CompletionProvider provider) { + super(provider); + } + + @Override + protected AutoCompleteWithExtraRefreshPopupWindow createAutoCompletePopupWindow() { + JSAutoCompletePopupWindow popupWindow = new JSAutoCompletePopupWindow(getParentWindow(),this); + popupWindow.applyComponentOrientation( + getTextComponentOrientation()); + if (getCellRender() != null) { + popupWindow.setListCellRenderer(getCellRender()); + } + if (getPreferredChoicesWindowSize() != null) { + popupWindow.setSize(getPreferredChoicesWindowSize()); + } + if (getPreferredDescWindowSize() != null) { + popupWindow.setDescriptionWindowSize( + getPreferredDescWindowSize()); + } + return popupWindow; + } +} diff --git a/designer-base/src/main/java/com/fr/design/javascript/JSContentPane.java b/designer-base/src/main/java/com/fr/design/javascript/JSContentPane.java index 10e11c8fb..10bee69d2 100644 --- a/designer-base/src/main/java/com/fr/design/javascript/JSContentPane.java +++ b/designer-base/src/main/java/com/fr/design/javascript/JSContentPane.java @@ -4,7 +4,9 @@ import com.fr.design.DesignerEnvManager; import com.fr.design.border.UIRoundedBorder; import com.fr.design.constants.KeyWords; import com.fr.design.constants.UIConstants; +import com.fr.design.dialog.BasicDialog; import com.fr.design.dialog.BasicPane; +import com.fr.design.dialog.DialogActionAdapter; import com.fr.design.gui.autocomplete.AutoCompletion; import com.fr.design.gui.autocomplete.BasicCompletion; import com.fr.design.gui.autocomplete.CompletionProvider; @@ -15,31 +17,145 @@ import com.fr.design.gui.ilable.UILabel; import com.fr.design.gui.syntax.ui.rsyntaxtextarea.RSyntaxTextArea; import com.fr.design.gui.syntax.ui.rsyntaxtextarea.SyntaxConstants; import com.fr.design.javascript.beautify.JavaScriptFormatHelper; +import com.fr.design.javascript.jsapi.JSImplPopulateAction; +import com.fr.design.javascript.jsapi.JSImplUpdateAction; import com.fr.design.layout.FRGUIPaneFactory; +import com.fr.design.mainframe.DesignerContext; import com.fr.general.IOUtils; +import com.fr.js.JavaScriptImpl; -import javax.swing.*; -import java.awt.*; +import java.awt.BorderLayout; +import java.awt.Cursor; +import java.awt.Dimension; +import java.awt.FontMetrics; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.ArrayList; +import javax.swing.JPanel; +import javax.swing.KeyStroke; +import javax.swing.SwingConstants; +import javax.swing.SwingWorker; public class JSContentPane extends BasicPane { - private RSyntaxTextArea contentTextArea; - private UILabel funNameLabel; + protected RSyntaxTextArea contentTextArea; + private UILabel funNameLabel = new UILabel(); private AutoCompletion ac; private static final Dimension FUNCTION_NAME_LABEL_SIZE = new Dimension(300, 80); - + private String[] defaultArgs; private int titleWidth = 180; + private JPanel labelPane = new JPanel(new BorderLayout(6, 4));; + private NewJavaScriptImplPane newJavaScriptImplPane = null; + private JavaScriptImpl javaScript; + private JSImplUpdateAction jsImplUpdateAction; + private JSImplPopulateAction jsImplPopulateAction; + private boolean modal; + BasicDialog advancedEditorDialog ; + public JSContentPane(){} public JSContentPane(String[] args) { + defaultArgs = args; this.setLayout(FRGUIPaneFactory.createBorderLayout()); - funNameLabel = new UILabel(); - this.setFunctionTitle(args); + initFunctionTitle(args); + + JPanel jsParaPane = createJSParaPane(); + addNewPaneLabel(); + this.add(jsParaPane, BorderLayout.NORTH); + + UIScrollPane sp = createContentTextAreaPanel(); + initContentTextAreaListener(); + this.add(sp, BorderLayout.CENTER); + + UILabel funNameLabel2 = new UILabel(); + funNameLabel2.setText("}"); + this.add(funNameLabel2, BorderLayout.SOUTH); + } + public JSContentPane(String[] args,boolean modal) { + this(args); + this.modal = modal; + } + + + public void setJsImplUpdateAction(JSImplUpdateAction jsImplUpdateAction){ + this.jsImplUpdateAction = jsImplUpdateAction; + } + + public void setJsImplPopulateAction(JSImplPopulateAction jsImplPopulateAction){ + this.jsImplPopulateAction = jsImplPopulateAction; + } + + public void updateJSImpl(JavaScriptImpl javaScript){ + this.javaScript = javaScript; + } + + + private void addNewPaneLabel(){ + UILabel advancedEditorLabel = new UILabel(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Advanced_Editor"), SwingConstants.LEFT); + advancedEditorLabel.setCursor(new Cursor(Cursor.HAND_CURSOR)); + advancedEditorLabel.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if(newJavaScriptImplPane == null){ + newJavaScriptImplPane = new NewJavaScriptImplPane(defaultArgs); + } + jsImplUpdateAction.update(javaScript); + newJavaScriptImplPane.populate(javaScript); + if(advancedEditorDialog == null || !advancedEditorDialog.isVisible()) { + advancedEditorDialog = newJavaScriptImplPane.showWindow(DesignerContext.getDesignerFrame(), new DialogActionAdapter() { + @Override + public void doOk() { + if (javaScript != null) { + newJavaScriptImplPane.updateBean(javaScript); + jsImplPopulateAction.populate(javaScript); + } + } + + @Override + public void doCancel() { + super.doCancel(); + } + }); + advancedEditorDialog.setModal(modal); + advancedEditorDialog.setResizable(true); + advancedEditorDialog.pack(); + advancedEditorDialog.setVisible(true); + } + advancedEditorDialog.requestFocus(); + } + }); + labelPane.add(advancedEditorLabel,BorderLayout.CENTER); + } + + protected UIScrollPane createContentTextAreaPanel(){ + contentTextArea = new RSyntaxTextArea(); + contentTextArea.setCloseCurlyBraces(true); + contentTextArea.setLineWrap(true); + contentTextArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVASCRIPT); + contentTextArea.setCodeFoldingEnabled(true); + contentTextArea.setAntiAliasingEnabled(true); + return new UIScrollPane(contentTextArea); + } + + private void initContentTextAreaListener(){ + contentTextArea.addFocusListener(new FocusListener() { + @Override + public void focusGained(FocusEvent e) { + // 获得焦点时 安装 + installAutoCompletion(); + } + + @Override + public void focusLost(FocusEvent e) { + // 失去焦点时 卸载 + uninstallAutoCompletion(); + } + }); + } + + protected JPanel createJSParaPane(){ UILabel label = new UILabel(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Format_JavaScript"), IOUtils.readIcon("com/fr/design/images/edit/format.png"), SwingConstants.LEFT); label.setCursor(new Cursor(Cursor.HAND_CURSOR)); label.setToolTipText(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Format_JavaScript")); @@ -65,43 +181,20 @@ public class JSContentPane extends BasicPane { } }); - //REPORT-10533 用户参数多达25个,导致JS没地方写,增加滚动条显示 + labelPane.add(label,BorderLayout.EAST); JPanel jsParaPane = new JPanel(new BorderLayout(4, 4)); jsParaPane.setPreferredSize(new Dimension(300, 80)); UIScrollPane scrollPane = new UIScrollPane(funNameLabel); scrollPane.setPreferredSize(FUNCTION_NAME_LABEL_SIZE); scrollPane.setBorder(new UIRoundedBorder(UIConstants.TITLED_BORDER_COLOR, 1, UIConstants.ARC)); jsParaPane.add(scrollPane, BorderLayout.WEST); - jsParaPane.add(label, BorderLayout.EAST); - this.add(jsParaPane, BorderLayout.NORTH); - - contentTextArea = new RSyntaxTextArea(); - contentTextArea.setCloseCurlyBraces(true); - contentTextArea.setLineWrap(true); - contentTextArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVASCRIPT); - contentTextArea.setCodeFoldingEnabled(true); - contentTextArea.setAntiAliasingEnabled(true); - - UIScrollPane sp = new UIScrollPane(contentTextArea); - this.add(sp, BorderLayout.CENTER); - - contentTextArea.addFocusListener(new FocusListener() { - @Override - public void focusGained(FocusEvent e) { - // 获得焦点时 安装 - installAutoCompletion(); - } - - @Override - public void focusLost(FocusEvent e) { - // 失去焦点时 卸载 - uninstallAutoCompletion(); - } - }); + jsParaPane.add(labelPane, BorderLayout.EAST); + return jsParaPane; + } - UILabel funNameLabel2 = new UILabel(); - funNameLabel2.setText("}"); - this.add(funNameLabel2, BorderLayout.SOUTH); + protected void initFunctionTitle(String[] args){ + funNameLabel = new UILabel(); + this.setFunctionTitle(args); } private KeyStroke convert2KeyStroke(String ks) { diff --git a/designer-base/src/main/java/com/fr/design/javascript/JSContentWithDescriptionPane.java b/designer-base/src/main/java/com/fr/design/javascript/JSContentWithDescriptionPane.java new file mode 100644 index 000000000..b1516fe36 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/javascript/JSContentWithDescriptionPane.java @@ -0,0 +1,858 @@ +package com.fr.design.javascript; + +import com.fr.design.border.UIRoundedBorder; +import com.fr.design.constants.UIConstants; +import com.fr.design.gui.autocomplete.AutoCompleteExtraRefreshComponent; +import com.fr.design.gui.autocomplete.BasicCompletion; +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.JSImplPaneAutoCompletion; +import com.fr.design.gui.ibutton.UIButton; +import com.fr.design.gui.icontainer.UIScrollPane; +import com.fr.design.gui.ilable.UILabel; +import com.fr.design.gui.itextarea.UITextArea; +import com.fr.design.gui.itextfield.UITextField; +import com.fr.design.i18n.Toolkit; +import com.fr.design.javascript.jsapi.JSAPITreeHelper; +import com.fr.design.javascript.jsapi.JSAPIUserObject; +import com.fr.design.layout.FRGUIPaneFactory; +import com.fr.general.CloudCenter; +import com.fr.general.ComparatorUtils; +import com.fr.general.http.HttpToolbox; +import com.fr.json.JSONArray; +import com.fr.json.JSONException; +import com.fr.json.JSONObject; +import com.fr.log.FineLoggerFactory; +import com.fr.stable.StringUtils; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.Desktop; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import javax.swing.BorderFactory; +import javax.swing.DefaultListCellRenderer; +import javax.swing.DefaultListModel; +import javax.swing.JComponent; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JScrollPane; +import javax.swing.JTree; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeCellRenderer; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreeNode; +import javax.swing.tree.TreePath; + +public class JSContentWithDescriptionPane extends JSContentPane implements KeyListener { + + //搜索关键词输入框 + private UITextField keyWordTextField = new UITextField(16); + //搜索出的提示列表 + private JList tipsList; + private DefaultListModel tipsListModel = new DefaultListModel(); + + private JList interfaceNameList; + private DefaultListModel interfaceNameModel; + + private JTree moduleTree; + + private DefaultCompletionProvider completionProvider; + private JSImplPaneAutoCompletion autoCompletion; + + //函数说明文本框 + private UITextArea descriptionTextArea; + + private JPopupMenu popupMenu; + + private InterfaceAndDescriptionPanel interfaceAndDescriptionPanel; + private JList helpDOCList; + + private int ifHasBeenWriten = 0; + private int currentPosition = 0; + private int beginPosition = 0; + private int insertPosition = 0; + private static final String SEPARATOR = "_"; + + private static final int KEY_10 = 10; + //上下左右 + private static final int KEY_37 = 37; + private static final int KEY_38 = 38; + private static final int KEY_39 = 39; + private static final int KEY_40 = 40; + + private static final String URL_FOR_TEST_NETWORK = "https://www.baidu.com"; + + private static final String DOCUMENT_SEARCH_URL = CloudCenter.getInstance().acquireUrlByKind("af.doc_search"); + + private String currentValue; + + public JSContentWithDescriptionPane(String[] args) { + this.setLayout(new BorderLayout()); + //=============================== + this.initFunctionTitle(args); + JPanel jsParaAndSearchPane = new JPanel(new BorderLayout()); + + //js函数声明面板 + JPanel jsParaPane = createJSParaPane(); + + jsParaPane.setPreferredSize(new Dimension(650, 80)); + //右上角的搜索提示面板 + JPanel tipsPane = createTipsPane(); + + jsParaAndSearchPane.add(jsParaPane, BorderLayout.CENTER); + jsParaAndSearchPane.add(tipsPane, BorderLayout.EAST); + + initPopTips(); + + //js文本编辑面板 + UIScrollPane contentTextAreaPanel = createContentTextAreaPanel(); + initContextAreaListener(); + + contentTextAreaPanel.setPreferredSize(new Dimension(850, 250)); + //js函数结束标签 + UILabel endBracketsLabel = new UILabel(); + endBracketsLabel.setText("}"); + + //结尾括号和复用函数按钮面板 + JPanel endBracketsPanel = FRGUIPaneFactory.createBorderLayout_S_Pane(); + endBracketsPanel.add(endBracketsLabel, BorderLayout.WEST); + + JPanel northPanel = FRGUIPaneFactory.createBorderLayout_S_Pane(); + northPanel.add(jsParaAndSearchPane, BorderLayout.NORTH); + + northPanel.add(contentTextAreaPanel, BorderLayout.CENTER); + northPanel.add(endBracketsPanel, BorderLayout.SOUTH); + + //主编辑框,也就是面板的正中间部分 + this.add(northPanel, BorderLayout.CENTER); + + //函数分类和函数说明面板================================== + JPanel functionNameAndDescriptionPanel = createInterfaceAndDescriptionPanel(); + functionNameAndDescriptionPanel.setPreferredSize(new Dimension(880, 220)); + + this.add(functionNameAndDescriptionPanel, BorderLayout.SOUTH); + } + + private void initContextAreaListener() { + contentTextArea.addKeyListener(new KeyAdapter() { + @Override + public void keyTyped(KeyEvent e) { + if ((e.getKeyChar() >= 'A' && e.getKeyChar() <= 'z') || e.getKeyChar() == '_') { + if (autoCompletion != null) { + autoCompletion.doCompletion(); + } + } + } + + @Override + public void keyReleased(KeyEvent e) { + contentTextArea.setForeground(Color.black); + } + }); + contentTextArea.addKeyListener(this); + contentTextArea.addFocusListener(new FocusAdapter() { + @Override + public void focusGained(FocusEvent e) { + if (autoCompletion == null) { + installAutoCompletion(); + } + } + + @Override + public void focusLost(FocusEvent e) { + uninstallAutoCompletion(); + } + }); + } + + @Override + public void keyTyped(KeyEvent e) { + + } + + @Override + public void keyPressed(KeyEvent e) { + if (ifHasBeenWriten == 0) { + this.contentTextArea.setText(StringUtils.EMPTY); + } + } + + @Override + public void keyReleased(KeyEvent e) { + int key = e.getKeyCode(); + if (key == KEY_38 || key == KEY_40 || key == KEY_37 || key == KEY_39 || key == KEY_10) //如果是删除符号 ,为了可读性 没有和其他按键的程序相融合 + { + currentPosition = contentTextArea.getCaretPosition(); + insertPosition = currentPosition; + beginPosition = getBeginPosition(); + } else { + if (contentTextArea.getText().trim().length() == 0) { + insertPosition = 0; + } else { + contentTextArea.setForeground(Color.black); + currentPosition = contentTextArea.getCaretPosition(); + beginPosition = getBeginPosition(); + insertPosition = beginPosition; + ifHasBeenWriten = 1; + } + } + } + + private int getBeginPosition() { + int i = currentPosition; + String textArea = contentTextArea.getText(); + for (; i > 0; i--) { + String tested = textArea.substring(i - 1, i).toUpperCase(); + char[] testedChar = tested.toCharArray(); + if (isChar(testedChar[0]) || isNum(testedChar[0])) { + continue; + } else { + break; + } + } + return i; + } + + private static boolean isNum(char tested) { + return tested >= '0' && tested <= '9'; + } + + private boolean isChar(char tested) { + return tested >= 'A' && tested <= 'Z' || tested >= 'a' && tested < 'z'; + } + + public class InterfaceAndDescriptionPanel extends JPanel implements AutoCompleteExtraRefreshComponent { + @Override + public void refresh(String replacementText) { + fixInterfaceNameList(replacementText); + } + } + + private void fixInterfaceNameList(String interfaceName) { + DefaultTreeModel defaultTreeModel = (DefaultTreeModel) moduleTree.getModel(); + TreeNode root = (TreeNode) defaultTreeModel.getRoot(); + String directCategory = JSAPITreeHelper.getDirectCategory(interfaceName); + if (directCategory == null) { + return; + } + setModuleTreeSelection(root, directCategory, defaultTreeModel); + interfaceNameModel = (DefaultListModel) interfaceNameList.getModel(); + interfaceNameModel.clear(); + List interfaceNames = JSAPITreeHelper.getNames(directCategory); + int index = 0; + for (int i = 0; i < interfaceNames.size(); i++) { + interfaceNameModel.addElement(interfaceNames.get(i)); + if (StringUtils.equals(interfaceNames.get(i), interfaceName)) { + index = i; + } + } + interfaceNameList.setSelectedIndex(index); + interfaceNameList.ensureIndexIsVisible(index); + } + + private boolean setModuleTreeSelection(TreeNode node, String directCategory, DefaultTreeModel treeModel) { + + DefaultMutableTreeNode defaultMutableTreeNode = (DefaultMutableTreeNode) node; + Object userObject = defaultMutableTreeNode.getUserObject(); + if (userObject instanceof JSAPIUserObject) { + String value = ((JSAPIUserObject) userObject).getValue(); + if (StringUtils.equals(value, directCategory)) { + moduleTree.setSelectionPath(new TreePath(treeModel.getPathToRoot(node))); + return true; + } + return false; + } + for (int i = 0; i < node.getChildCount(); i++) { + if (setModuleTreeSelection(node.getChildAt(i), directCategory, treeModel)) { + return true; + } + } + return false; + } + + private JPanel createInterfaceAndDescriptionPanel() { + interfaceAndDescriptionPanel = new InterfaceAndDescriptionPanel(); + interfaceAndDescriptionPanel.setLayout(new BorderLayout(4, 4)); + JPanel interfacePanel = new JPanel(new BorderLayout(4, 4)); + interfaceAndDescriptionPanel.add(interfacePanel, BorderLayout.WEST); + JPanel descriptionAndDocumentPanel = new JPanel(new BorderLayout(4, 4)); + //函数说明和帮助文档框 + initDescriptionArea(descriptionAndDocumentPanel); + + //模块和接口面板 + initInterfaceModuleTree(interfacePanel); + initInterfaceNameList(interfacePanel); + + initHelpDocumentPane(descriptionAndDocumentPanel); + + interfaceAndDescriptionPanel.add(descriptionAndDocumentPanel, BorderLayout.CENTER); + return interfaceAndDescriptionPanel; + } + + private void doHelpDocumentSearch() { + Object value = interfaceNameList.getSelectedValue(); + if (value != null) { + String url = DOCUMENT_SEARCH_URL + value.toString(); + try { + String result = HttpToolbox.get(url); + JSONObject jsonObject = new JSONObject(result); + JSONArray jsonArray = jsonObject.optJSONArray("list"); + if (jsonArray != null) { + DefaultListModel helpDOCModel = (DefaultListModel) helpDOCList.getModel(); + helpDOCModel.clear(); + for (int i = 0; i < jsonArray.length(); i++) { + JSONObject resultJSONObject = jsonArray.optJSONObject(i); + String docURL = resultJSONObject.optString("url"); + String name = resultJSONObject.optString("title").trim(); + HelpDocument helpDocument = new HelpDocument(docURL, name); + helpDOCModel.addElement(helpDocument); + } + } + } catch (JSONException e) { + FineLoggerFactory.getLogger().debug(e.getMessage(), e); + } catch (Exception e) { + FineLoggerFactory.getLogger().error(e.getMessage(), e); + } + } + } + + private void initHelpDocumentPane(JPanel descriptionAndDocumentPanel) { + UIScrollPane helpDOCScrollPane; + if (isNetworkOk()) { + helpDOCList = new JList(new DefaultListModel()); + initHelpDOCListRender(); + initHelpDOCListListener(); + helpDOCScrollPane = new UIScrollPane(helpDOCList); + doHelpDocumentSearch(); + } else { + UILabel label1 = new UILabel(Toolkit.i18nText("Fine-Design_Net_Connect_Failed"), 0); + label1.setPreferredSize(new Dimension(180, 20)); + UILabel label2 = new UILabel(Toolkit.i18nText("Fine-Design_Basic_Reload"), 0); + label2.setPreferredSize(new Dimension(180, 20)); + label2.setForeground(Color.blue); + JPanel labelPane = FRGUIPaneFactory.createVerticalFlowLayout_Pane(true, 0, 0, 0); + labelPane.setBackground(Color.WHITE); + labelPane.add(label1); + labelPane.add(label2); + JPanel containerPanel = FRGUIPaneFactory.createBorderLayout_S_Pane(); + containerPanel.add(labelPane, BorderLayout.CENTER); + helpDOCScrollPane = new UIScrollPane(containerPanel); + label2.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + descriptionAndDocumentPanel.removeAll(); + initHelpDocumentPane(descriptionAndDocumentPanel); + + } + }); + } + helpDOCScrollPane.setPreferredSize(new Dimension(200, 200)); + helpDOCScrollPane.setBorder(null); + descriptionAndDocumentPanel.add(this.createNamePane(Toolkit.i18nText("Fine-Design_Relevant_Cases"), helpDOCScrollPane), BorderLayout.EAST); + + } + + private void initHelpDOCListListener() { + helpDOCList.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { + Object value = helpDOCList.getSelectedValue(); + if (value instanceof HelpDocument) { + String url = ((HelpDocument) value).getDocumentUrl(); + try { + Desktop.getDesktop().browse(new URI(url)); + } catch (IOException ex) { + FineLoggerFactory.getLogger().error(ex.getMessage(), ex); + } catch (URISyntaxException ex) { + FineLoggerFactory.getLogger().error(ex.getMessage(), ex); + } + } + } + } + }); + } + + private void initHelpDOCListRender() { + helpDOCList.setCellRenderer(new DefaultListCellRenderer() { + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + if (value instanceof HelpDocument) { + this.setText(((HelpDocument) value).getName()); + this.setForeground(Color.BLUE); + } + return this; + } + }); + } + + private static boolean isNetworkOk() { + try { + HttpToolbox.get(URL_FOR_TEST_NETWORK); + return true; + } catch (Exception ignore) { + // 网络异常 + return false; + } + } + + private static class HelpDocument { + private String documentUrl; + + + private String name; + + public HelpDocument(String documentUrl, String name) { + this.documentUrl = documentUrl; + this.name = name; + } + + public String getDocumentUrl() { + return documentUrl; + } + + public String getName() { + return name; + } + } + + private void initDescriptionArea(JPanel descriptionPanel) { + descriptionTextArea = new UITextArea(); + UIScrollPane descriptionScrollPane = new UIScrollPane(descriptionTextArea); + descriptionScrollPane.setPreferredSize(new Dimension(300, 200)); + descriptionPanel.add(this.createNamePane(Toolkit.i18nText("Fine-Design_Interface_Description"), descriptionScrollPane), BorderLayout.CENTER); + descriptionTextArea.setBackground(Color.white); + descriptionTextArea.setLineWrap(true); + descriptionTextArea.setWrapStyleWord(true); + descriptionTextArea.setEditable(false); + } + + private void installAutoCompletion() { + CompletionProvider provider = createCompletionProvider(); + autoCompletion = new JSImplPaneAutoCompletion(provider); + autoCompletion.setListCellRenderer(new CompletionCellRenderer()); + autoCompletion.install(contentTextArea); + autoCompletion.installExtraRefreshComponent(interfaceAndDescriptionPanel); + } + + private void uninstallAutoCompletion() { + if (autoCompletion != null) { + autoCompletion.uninstall(); + autoCompletion = null; + } + } + + private CompletionProvider createCompletionProvider() { + if (completionProvider == null) { + completionProvider = new DefaultCompletionProvider(); + for (String name : JSAPITreeHelper.getAllNames()) { + completionProvider.addCompletion(new BasicCompletion(completionProvider, name)); + } + } + return completionProvider; + } + + private void initInterfaceModuleTree(JPanel interfacePanel) { + moduleTree = new JTree(); + UIScrollPane moduleTreePane = new UIScrollPane(moduleTree); + moduleTreePane.setBorder(new UIRoundedBorder(UIConstants.LINE_COLOR, 1, UIConstants.ARC)); + interfacePanel.add(this.createNamePane(Toolkit.i18nText("Fine-Design_Module"), moduleTreePane), BorderLayout.WEST); + moduleTreePane.setPreferredSize(new Dimension(180, 200)); + + moduleTree.setRootVisible(false); + moduleTree.setShowsRootHandles(true); + moduleTree.setCellRenderer(moduleTreeCellRender); + DefaultTreeModel moduleTreeModel = (DefaultTreeModel) moduleTree.getModel(); + DefaultMutableTreeNode rootNode = (DefaultMutableTreeNode) moduleTreeModel.getRoot(); + rootNode.removeAllChildren(); + + JSAPITreeHelper.createJSAPITree(rootNode); + moduleTreeModel.reload(); + + initModuleTreeSelectionListener(); + } + + private void initModuleTreeSelectionListener() { + moduleTree.addTreeSelectionListener(new TreeSelectionListener() { + @Override + public void valueChanged(TreeSelectionEvent e) { + DefaultMutableTreeNode selectedTreeNode = (DefaultMutableTreeNode) moduleTree.getLastSelectedPathComponent(); + Object selectedValue = selectedTreeNode.getUserObject(); + if (null == selectedValue) { + return; + } + if (selectedValue instanceof JSAPIUserObject) { + interfaceNameModel = (DefaultListModel) interfaceNameList.getModel(); + interfaceNameModel.clear(); + String text = ((JSAPIUserObject) selectedValue).getValue(); + List allInterfaceNames = JSAPITreeHelper.getNames(text); + for (String interfaceName : allInterfaceNames) { + interfaceNameModel.addElement(interfaceName); + } + if (interfaceNameModel.size() > 0) { + interfaceNameList.setSelectedIndex(0); + setDescription(interfaceNameList.getSelectedValue().toString()); + interfaceNameList.ensureIndexIsVisible(0); + } + } + } + }); + } + + + private DefaultTreeCellRenderer moduleTreeCellRender = new DefaultTreeCellRenderer() { + public Component getTreeCellRendererComponent(JTree tree, + Object value, boolean selected, boolean expanded, + boolean leaf, int row, boolean hasFocus) { + super.getTreeCellRendererComponent(tree, value, selected, + expanded, leaf, row, hasFocus); + + DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode) value; + Object userObj = treeNode.getUserObject(); + if (userObj instanceof JSAPIUserObject) { + this.setText(((JSAPIUserObject) userObj).getDisplayText()); + this.setIcon(null); + } + return this; + } + + }; + + + private void initInterfaceNameList(JPanel interfacePanel) { + interfaceNameList = new JList(new DefaultListModel()); + UIScrollPane interfaceNamePanelScrollPane = new UIScrollPane(interfaceNameList); + interfaceNamePanelScrollPane.setPreferredSize(new Dimension(180, 200)); + interfacePanel.add( + this.createNamePane(Toolkit.i18nText("Fine-Design_Interface") + ":", interfaceNamePanelScrollPane), + BorderLayout.CENTER); + + interfaceNamePanelScrollPane.setBorder(new UIRoundedBorder(UIConstants.LINE_COLOR, 1, UIConstants.ARC)); + initInterfaceNameModule(); + initInterfaceNameListSelectionListener(); + initInterfaceNameListMouseListener(); + } + + private void initInterfaceNameModule() { + moduleTree.setSelectionPath(moduleTree.getPathForRow(0)); + } + + private void setDescription(String interfaceName) { + StringBuilder il8Key = new StringBuilder(); + moduleTree.getSelectionPath().getPath(); + Object obj = moduleTree.getSelectionPath().getPath()[moduleTree.getSelectionPath().getPath().length - 1]; + Object userObject = ((DefaultMutableTreeNode) obj).getUserObject(); + if (userObject instanceof JSAPIUserObject) { + il8Key.append(JSAPITreeHelper.getDirectCategory(interfaceName)); + } + interfaceName = interfaceName.toUpperCase(); + if (!interfaceName.startsWith(SEPARATOR)) { + interfaceName = SEPARATOR + interfaceName; + } + il8Key.append(interfaceName); + descriptionTextArea.setText(Toolkit.i18nText(il8Key.toString())); + descriptionTextArea.moveCaretPosition(0); + } + + private void initInterfaceNameListSelectionListener() { + interfaceNameList.addListSelectionListener(new ListSelectionListener() { + + public void valueChanged(ListSelectionEvent evt) { + Object selectedValue = interfaceNameList.getSelectedValue(); + if (selectedValue == null) { + return; + } + String interfaceName = selectedValue.toString(); + if (!StringUtils.equals(interfaceName, currentValue)) { + setDescription(interfaceName); + doHelpDocumentSearch(); + currentValue = interfaceName; + } + + } + }); + } + + + private void initInterfaceNameListMouseListener() { + interfaceNameList.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent evt) { + if (evt.getClickCount() >= 2) { + Object selectedValue = interfaceNameList.getSelectedValue(); + String interfaceName = selectedValue.toString(); + applyText(interfaceName); + } + } + }); + } + + private void applyText(String text) { + if (text == null || text.length() <= 0) { + return; + } + if (ifHasBeenWriten == 0) { + contentTextArea.setForeground(Color.black); + contentTextArea.setText(StringUtils.EMPTY); + ifHasBeenWriten = 1; + insertPosition = 0; + } + String textAll = contentTextArea.getText(); + currentPosition = contentTextArea.getCaretPosition(); + int insert = 0; + int current = 0; + if (insertPosition <= currentPosition) { + insert = insertPosition; + current = currentPosition; + } else { + insert = currentPosition; + current = insertPosition; + } + String beforeIndexOfInsertString = textAll.substring(0, insert); + String afterIndexofInsertString = textAll.substring(current); + contentTextArea.setText(beforeIndexOfInsertString + text + afterIndexofInsertString); + contentTextArea.getText(); + contentTextArea.requestFocus(); + insertPosition = contentTextArea.getCaretPosition(); + } + + private JPanel createNamePane(String name, JComponent comp) { + JPanel namePane = new JPanel(new BorderLayout(4, 4)); + namePane.add(new UILabel(name), BorderLayout.NORTH); + namePane.add(comp, BorderLayout.CENTER); + return namePane; + } + + private JPanel createTipsPane() { + JPanel tipsPane = FRGUIPaneFactory.createBorderLayout_S_Pane(); + tipsPane.setLayout(new BorderLayout(4, 4)); + tipsPane.setBorder(BorderFactory.createEmptyBorder(30, 2, 0, 0)); + JPanel searchPane = FRGUIPaneFactory.createBorderLayout_S_Pane(); + searchPane.setLayout(new BorderLayout(4, 4)); + searchPane.add(keyWordTextField, BorderLayout.CENTER); + + //搜索按钮 + UIButton searchButton = new UIButton(Toolkit.i18nText("Fine-Design_Basic_Search")); + searchButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + String toFind = keyWordTextField.getText(); + search(toFind); + popTips(); + tipsList.requestFocusInWindow(); + } + }); + + keyWordTextField.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyChar() == KeyEvent.VK_ENTER) { + e.consume(); + String toFind = keyWordTextField.getText(); + search(toFind); + popTips(); + tipsList.requestFocusInWindow(); + } + } + }); + + searchPane.add(searchButton, BorderLayout.EAST); + tipsPane.add(searchPane, BorderLayout.NORTH); + + tipsList = new JList(tipsListModel); + tipsList.addMouseListener(tipsListMouseListener); + tipsList.addListSelectionListener(tipsListSelectionListener); + tipsList.addKeyListener(tipListKeyListener); + + return tipsPane; + } + + private void search(String key) { + tipsListModel.removeAllElements(); + tipsListModel.clear(); + key = key.replaceAll(StringUtils.BLANK, StringUtils.EMPTY); + ArrayList list = new ArrayList<>(); + if (!StringUtils.isEmpty(key)) { + List allNames = JSAPITreeHelper.getAllNames(); + for (String name : allNames) { + if (searchResult(key, name)) { + list.add(name); + } + } + String finalKey = key; + Collections.sort(list, new Comparator() { + @Override + public int compare(String o1, String o2) { + int result; + boolean o1StartWidth = o1.toLowerCase().startsWith(finalKey.toLowerCase()); + boolean o2StartWidth = o2.toLowerCase().startsWith(finalKey.toLowerCase()); + if (o1StartWidth) { + result = o2StartWidth ? o1.compareTo(o2) : -1; + } else { + result = o2StartWidth ? 1 : o1.compareTo(o2); + } + return result; + } + }); + for (String name : list) { + tipsListModel.addElement(name); + } + if (!tipsListModel.isEmpty()) { + tipsList.setSelectedIndex(0); + } + } + } + + private boolean searchResult(String key, String interfaceName) { + if (StringUtils.isBlank(key) || StringUtils.isBlank(interfaceName)) { + return false; + } + int length = key.length(); + String temp = interfaceName.toUpperCase(); + for (int i = 0; i < length; i++) { + String check = key.substring(i, i + 1); + int index = temp.indexOf(check.toUpperCase()); + if (index == -1) { + return false; + } else { + temp = temp.substring(index + 1); + } + } + return true; + } + + private void initPopTips() { + popupMenu = new JPopupMenu(); + JScrollPane tipsScrollPane = new JScrollPane(tipsList); + popupMenu.add(tipsScrollPane); + tipsScrollPane.setPreferredSize(new Dimension(220, 146)); + tipsScrollPane.setBorder(new UIRoundedBorder(UIConstants.LINE_COLOR, 1, UIConstants.ARC)); + } + + private void popTips() { + popupMenu.show(keyWordTextField, 0, 23); + } + + private ListSelectionListener tipsListSelectionListener = new ListSelectionListener() { + @Override + public void valueChanged(ListSelectionEvent e) { + Object selectValue = tipsList.getSelectedValue(); + if (selectValue == null) { + return; + } + String interfaceName = selectValue.toString(); + fixInterfaceNameList(interfaceName); + } + }; + + private KeyListener tipListKeyListener = new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyChar() == KeyEvent.VK_ENTER) { + Object selectValue = tipsList.getSelectedValue(); + if (selectValue == null) { + return; + } + tipListValueSelectAction(selectValue.toString()); + if (popupMenu != null) { + popupMenu.setVisible(false); + } + contentTextArea.requestFocusInWindow(); + } + } + }; + + private void tipListValueSelectAction(String value) { + if (ifHasBeenWriten == 0) { + contentTextArea.setForeground(Color.black); + contentTextArea.setText(StringUtils.EMPTY); + } + contentTextArea.setForeground(Color.black); + currentPosition = contentTextArea.getCaretPosition(); + String output = value; + String textAll = contentTextArea.getText(); + String textReplaced; + int position = 0; + if (insertPosition <= currentPosition) { + textReplaced = textAll.substring(0, insertPosition) + output + textAll.substring(currentPosition); + position = insertPosition + output.length(); + } else { + textReplaced = textAll.substring(0, currentPosition) + output + textAll.substring(insertPosition); + position = currentPosition + output.length(); + } + contentTextArea.setText(textReplaced); + contentTextArea.setCaretPosition(position); + insertPosition = position; + ifHasBeenWriten = 1; + tipsListModel.removeAllElements(); + } + + private MouseListener tipsListMouseListener = new MouseAdapter() { + String singlePressContent; + + String doublePressContent; + + @Override + public void mousePressed(MouseEvent e) { + int index = tipsList.getSelectedIndex(); + if (index != -1) { + if (e.getClickCount() == 1) { + singlePressContent = (String) tipsListModel.getElementAt(index); + } else if (e.getClickCount() == 2) { + doublePressContent = (String) tipsListModel.getElementAt(index); + } + } + } + + @Override + public void mouseReleased(MouseEvent e) { + int index = tipsList.getSelectedIndex(); + if (index != -1) { + if (e.getClickCount() == 1) { + if (ComparatorUtils.equals((String) tipsListModel.getElementAt(index), singlePressContent)) { + singleClickActuator(singlePressContent); + } + } else if (e.getClickCount() == 2) { + if (ComparatorUtils.equals((String) tipsListModel.getElementAt(index), doublePressContent)) { + doubleClickActuator(doublePressContent); + } + if (popupMenu != null) { + popupMenu.setVisible(false); + } + } + } + } + + private void singleClickActuator(String currentLineContent) { + setDescription(currentLineContent); + fixInterfaceNameList(currentLineContent); + } + + private void doubleClickActuator(String currentLineContent) { + tipListValueSelectAction(currentLineContent); + } + }; +} diff --git a/designer-base/src/main/java/com/fr/design/javascript/JavaScriptImplPane.java b/designer-base/src/main/java/com/fr/design/javascript/JavaScriptImplPane.java index 158c05d1e..6a83ad3d2 100644 --- a/designer-base/src/main/java/com/fr/design/javascript/JavaScriptImplPane.java +++ b/designer-base/src/main/java/com/fr/design/javascript/JavaScriptImplPane.java @@ -9,6 +9,8 @@ import com.fr.design.gui.itableeditorpane.UITableEditAction; import com.fr.design.gui.itableeditorpane.UITableEditorPane; import com.fr.design.gui.itextfield.UITextField; import com.fr.design.hyperlink.AbstractHyperLinkPane; +import com.fr.design.javascript.jsapi.JSImplPopulateAction; +import com.fr.design.javascript.jsapi.JSImplUpdateAction; import com.fr.design.mainframe.DesignerContext; import com.fr.design.scrollruler.ModLineBorder; import com.fr.design.utils.gui.GUICoreUtils; @@ -17,10 +19,16 @@ import com.fr.js.JavaScriptImpl; import com.fr.stable.ParameterProvider; import com.fr.stable.StringUtils; -import javax.swing.*; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.GridBagConstraints; +import java.awt.GridLayout; +import javax.swing.BorderFactory; +import javax.swing.JPanel; import javax.swing.event.TableModelEvent; import javax.swing.event.TableModelListener; -import java.awt.*; + import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -28,11 +36,11 @@ import java.util.List; public class JavaScriptImplPane extends AbstractHyperLinkPane { private static final int BOTTOM_BORDER = 12; private UITextField itemNameTextField; - private JSContentPane jsPane; + protected JSContentPane jsPane; private UITableEditorPane importedJsPane; private ReportletParameterViewPane parameterPane; private String[] defaultArgs; - + private boolean modal; public JavaScriptImplPane() { this(new String[0]); @@ -50,8 +58,57 @@ public class JavaScriptImplPane extends AbstractHyperLinkPane { initComponents(); } + public JavaScriptImplPane(String[] args, boolean modal) { + this.modal = modal; + this.defaultArgs = args; + initComponents(); + } + protected void initComponents() { - parameterPane = new ReportletParameterViewPane(getChartParaType(), getValueEditorPane(), getValueEditorPane()); + parameterPane = createParameterViewPane(); + importedJsPane = createImportedJsPane(); + importedJsPane.setPreferredSize(new Dimension(265, 150)); + + jsPane = createJSContentPane(defaultArgs); + jsPane.setBorder(BorderFactory.createTitledBorder(new ModLineBorder(ModLineBorder.TOP), com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Report_JavaScript"))); + + parameterPane.setPreferredSize(new Dimension(265, 150)); + JPanel topPane = new JPanel(new GridLayout(1,2)); + topPane.add(importedJsPane); + topPane.add(parameterPane); + + topPane.setBorder(BorderFactory.createEmptyBorder(0, 0, BOTTOM_BORDER, 0)); + + this.setLayout(new BorderLayout()); + this.add(topPane, BorderLayout.NORTH); + this.add(jsPane, BorderLayout.CENTER); + + this.reLayoutForChart(); + } + + protected JSContentPane createJSContentPane(String[] defaultArgs){ + JSContentPane jsContentPane= new JSContentPane(defaultArgs,modal); + jsContentPane.setJsImplUpdateAction(new JSImplUpdateAction() { + @Override + public void update(JavaScriptImpl javaScript) { + if(javaScript != null){ + updateBean(javaScript); + } + } + }); + jsContentPane.setJsImplPopulateAction(new JSImplPopulateAction() { + @Override + public void populate(JavaScriptImpl javaScript) { + if(javaScript != null){ + populateBean(javaScript); + } + } + }); + return jsContentPane; + } + + protected ReportletParameterViewPane createParameterViewPane(){ + ReportletParameterViewPane parameterPane = new ReportletParameterViewPane(getChartParaType(), getValueEditorPane(), getValueEditorPane()); parameterPane.setBorder(BorderFactory.createTitledBorder(new ModLineBorder(ModLineBorder.TOP), com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Parameter"))); parameterPane.addTableEditorListener(new TableModelListener() { public void tableChanged(TableModelEvent e) { @@ -72,7 +129,10 @@ public class JavaScriptImplPane extends AbstractHyperLinkPane { parameterChanger(list); } }); + return parameterPane; + } + protected UITableEditorPane createImportedJsPane(){ OneListTableModel model = new OneListTableModel(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Report_ReportServerP_Import_JavaScript"), this) { public UITableEditAction[] createAction() { @@ -84,26 +144,12 @@ public class JavaScriptImplPane extends AbstractHyperLinkPane { return new AddJsAction(); } }; - importedJsPane = new UITableEditorPane(model); + UITableEditorPane importedJsPane = new UITableEditorPane(model); importedJsPane.setBorder(BorderFactory.createTitledBorder(new ModLineBorder(ModLineBorder.TOP), com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Report_ReportServerP_Import_JavaScript"))); - importedJsPane.setPreferredSize(new Dimension(265, 150)); - jsPane = new JSContentPane(defaultArgs); - jsPane.setBorder(BorderFactory.createTitledBorder(new ModLineBorder(ModLineBorder.TOP), com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Report_JavaScript"))); + return importedJsPane; + } - parameterPane.setPreferredSize(new Dimension(265, 150)); - JPanel topPane = GUICoreUtils.createBorderLayoutPane( - importedJsPane, BorderLayout.CENTER, - parameterPane, BorderLayout.EAST - ); - topPane.setPreferredSize(new Dimension(300, 150)); - topPane.setBorder(BorderFactory.createEmptyBorder(0, 0, BOTTOM_BORDER, 0)); - this.setLayout(new BorderLayout()); - this.add(topPane, BorderLayout.NORTH); - this.add(jsPane, BorderLayout.CENTER); - - this.reLayoutForChart(); - } /** * 参数改变 @@ -140,10 +186,10 @@ public class JavaScriptImplPane extends AbstractHyperLinkPane { if (javaScriptImpl == null) { javaScriptImpl = new JavaScriptImpl(); jsPane.reset(); - }else{ + } else { jsPane.populate(javaScriptImpl.getContent()); } - + jsPane.updateJSImpl(javaScriptImpl); int rowCount = javaScriptImpl.getJSImportSize(); String[] value = new String[rowCount]; for (int i = 0; i < rowCount; i++) { @@ -160,6 +206,7 @@ public class JavaScriptImplPane extends AbstractHyperLinkPane { public JavaScriptImpl updateBean() { JavaScriptImpl javaScript = new JavaScriptImpl(); updateBean(javaScript); + jsPane.updateJSImpl(javaScript); return javaScript; } diff --git a/designer-base/src/main/java/com/fr/design/javascript/NewJavaScriptImplPane.java b/designer-base/src/main/java/com/fr/design/javascript/NewJavaScriptImplPane.java new file mode 100644 index 000000000..23226c6dd --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/javascript/NewJavaScriptImplPane.java @@ -0,0 +1,23 @@ +package com.fr.design.javascript; + + +import com.fr.js.JavaScriptImpl; + + +public class NewJavaScriptImplPane extends JavaScriptImplPane { + public NewJavaScriptImplPane(String[] args) { + super(args); + } + + protected JSContentPane createJSContentPane(String[] defaultArgs){ + return new JSContentWithDescriptionPane(defaultArgs); + } + + public void populate(JavaScriptImpl javaScript) { + if (javaScript != null) { + populateBean(javaScript); + } else { + jsPane.reset(); + } + } +} diff --git a/designer-base/src/main/java/com/fr/design/javascript/jsapi/CategoryTreeNodesUserObject.java b/designer-base/src/main/java/com/fr/design/javascript/jsapi/CategoryTreeNodesUserObject.java new file mode 100644 index 000000000..7b568eff7 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/javascript/jsapi/CategoryTreeNodesUserObject.java @@ -0,0 +1,21 @@ +package com.fr.design.javascript.jsapi; + +import com.fr.design.i18n.Toolkit; + +public class CategoryTreeNodesUserObject implements JSAPIUserObject { + private String value; + + public CategoryTreeNodesUserObject(String value) { + this.value = value; + } + + @Override + public String getValue() { + return value; + } + + @Override + public String getDisplayText() { + return Toolkit.i18nText(value); + } +} diff --git a/designer-base/src/main/java/com/fr/design/javascript/jsapi/JSAPITreeHelper.java b/designer-base/src/main/java/com/fr/design/javascript/jsapi/JSAPITreeHelper.java new file mode 100644 index 000000000..a5def0596 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/javascript/jsapi/JSAPITreeHelper.java @@ -0,0 +1,155 @@ +package com.fr.design.javascript.jsapi; + +import com.fr.general.IOUtils; +import com.fr.json.JSONArray; +import com.fr.json.JSONObject; +import com.fr.log.FineLoggerFactory; +import com.fr.stable.StringUtils; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import javax.swing.tree.DefaultMutableTreeNode; + +public class JSAPITreeHelper { + private static final String JSAPI_PATH = "com/fr/design/javascript/jsapi/jsapi.json"; + private static final String CATEGORY_PATH = "com/fr/design/javascript/jsapi/category.json"; + private static JSONObject categoryJSON ; + private static JSONObject jsapiJSON ; + + static { + jsapiJSON = createJSON(JSAPI_PATH); + categoryJSON = createJSON(CATEGORY_PATH); + } + + private static JSONObject createJSON(String path) { + StringBuilder jsonString = new StringBuilder(StringUtils.EMPTY); + try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(IOUtils.readResource(path)))) { + String s; + while ((s = bufferedReader.readLine()) != null) { + jsonString.append(s); + } + } catch (Exception e) { + FineLoggerFactory.getLogger().error(e.getMessage(), e); + } + return new JSONObject(jsonString.toString()); + } + + + public static void createJSAPITree(DefaultMutableTreeNode rootNode) { + createJSAPITree(categoryJSON, rootNode); + } + + public static String getDirectCategory(String name) { + if (jsapiJSON != null) { + Iterator it = jsapiJSON.keys(); + while (it.hasNext()) { + String key = it.next(); + JSONArray nameArray = jsapiJSON.optJSONArray(key); + for (int i = 0; i < nameArray.length(); i++) { + if (StringUtils.equals(nameArray.getString(i), name)) { + return key; + } + } + } + } + return null; + } + + private static void createJSAPITree(JSONObject jsonObject, DefaultMutableTreeNode rootNode) { + if (jsonObject != null && rootNode != null) { + Iterator it = jsonObject.keys(); + while (it.hasNext()) { + String key = it.next(); + JSONObject subNode = jsonObject.optJSONObject(key); + if (subNode.size() == 0) { + rootNode.add(new DefaultMutableTreeNode(new CategoryTreeNodesUserObject(key))); + } else { + DefaultMutableTreeNode treeNode = new DefaultMutableTreeNode(new CategoryTreeNodesUserObject(key)); + rootNode.add(treeNode); + createJSAPITree(subNode, treeNode); + } + } + } + } + + private static List getAllSubNodes(String name) { + return getAllSubNodes(name, categoryJSON); + } + + public static List getAllNames() { + ArrayList result = new ArrayList<>(); + if (jsapiJSON != null) { + Iterator it = jsapiJSON.keys(); + while (it.hasNext()) { + String key = it.next(); + JSONArray nameArray = jsapiJSON.optJSONArray(key); + for (int i = 0; i < nameArray.length(); i++) { + result.add(nameArray.getString(i)); + } + } + } + return result; + } + + public static List getNames(String category) { + ArrayList result = new ArrayList<>(); + List subCategories = getAllSubNodes(category); + if (jsapiJSON != null) { + for (String subCategory : subCategories) { + if (jsapiJSON.containsKey(subCategory)) { + JSONArray nameArray = jsapiJSON.optJSONArray(subCategory); + for (int i = 0; i < nameArray.length(); i++) { + result.add(nameArray.getString(i)); + } + } + } + } + return result; + } + + private static List getAllSubNodes(String name, JSONObject jsonObject) { + ArrayList result = new ArrayList<>(); + if (jsonObject != null) { + Iterator it = jsonObject.keys(); + while (it.hasNext()) { + String key = it.next(); + JSONObject subNode = jsonObject.optJSONObject(key); + if (subNode.size() == 0) { + if (StringUtils.equals(key, name)) { + result.add(key); + return result; + } + } else { + if (StringUtils.equals(key, name)) { + result.add(key); + result.addAll(getAllSubNodes(subNode)); + return result; + } else { + result.addAll(getAllSubNodes(name, subNode)); + } + } + } + } + return result; + } + + private static List getAllSubNodes(JSONObject jsonObject) { + ArrayList result = new ArrayList<>(); + if (jsonObject != null) { + Iterator it = jsonObject.keys(); + while (it.hasNext()) { + String key = it.next(); + JSONObject subNode = jsonObject.optJSONObject(key); + if (subNode.size() == 0) { + result.add(key); + } else { + result.add(key); + result.addAll(getAllSubNodes(subNode)); + } + } + } + return result; + } +} diff --git a/designer-base/src/main/java/com/fr/design/javascript/jsapi/JSAPIUserObject.java b/designer-base/src/main/java/com/fr/design/javascript/jsapi/JSAPIUserObject.java new file mode 100644 index 000000000..6790ec71e --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/javascript/jsapi/JSAPIUserObject.java @@ -0,0 +1,9 @@ +package com.fr.design.javascript.jsapi; + + +public interface JSAPIUserObject { + + String getValue(); + + String getDisplayText(); +} diff --git a/designer-base/src/main/java/com/fr/design/javascript/jsapi/JSImplPopulateAction.java b/designer-base/src/main/java/com/fr/design/javascript/jsapi/JSImplPopulateAction.java new file mode 100644 index 000000000..c33d7fe0e --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/javascript/jsapi/JSImplPopulateAction.java @@ -0,0 +1,7 @@ +package com.fr.design.javascript.jsapi; + +import com.fr.js.JavaScriptImpl; + +public interface JSImplPopulateAction { + void populate(JavaScriptImpl javaScript); +} diff --git a/designer-base/src/main/java/com/fr/design/javascript/jsapi/JSImplUpdateAction.java b/designer-base/src/main/java/com/fr/design/javascript/jsapi/JSImplUpdateAction.java new file mode 100644 index 000000000..b812d8b87 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/javascript/jsapi/JSImplUpdateAction.java @@ -0,0 +1,7 @@ +package com.fr.design.javascript.jsapi; + +import com.fr.js.JavaScriptImpl; + +public interface JSImplUpdateAction { + void update(JavaScriptImpl javaScript); +} diff --git a/designer-base/src/main/resources/com/fr/design/javascript/jsapi/category.json b/designer-base/src/main/resources/com/fr/design/javascript/jsapi/category.json new file mode 100644 index 000000000..ad1792976 --- /dev/null +++ b/designer-base/src/main/resources/com/fr/design/javascript/jsapi/category.json @@ -0,0 +1,47 @@ +{ + "Fine-Design_JSAPI_Public_Module": { + "Fine-Design_JSAPI_Public_Module_Global": { + "Fine-Design_JSAPI_Public_Module_Global_Universal": {}, + "Fine-Design_JSAPI_Public_Module_Global_FR": {}, + "Fine-Design_JSAPI_Public_Module_Global_FS": {}, + "Fine-Design_JSAPI_Public_Module_Global_Mobile": {} + }, + "Fine-Design_JSAPI_Public_Module_Widget": { + "Fine-Design_JSAPI_Public_Module_Widget_Get": {}, + "Fine-Design_JSAPI_Public_Module_Widget_Universal": {}, + "Fine-Design_JSAPI_Public_Module_Date_Widget_Peculiar": {}, + "Fine-Design_JSAPI_Public_Module_Button_Widget_Peculiar": {}, + "Fine-Design_JSAPI_Public_Module_Combobox_Widget_Peculiar": {} + }, + "Fine-Design_JSAPI_Public_Module_Table": { + "Fine-Design_JSAPI_Public_Module_Table_Marquee": {}, + "Fine-Design_JSAPI_Public_Module_Table_Scrollbar": {}, + "Fine-Design_JSAPI_Public_Module_Table_Cell_Style": {}, + "Fine-Design_JSAPI_Public_Module_Table_Row_Height_Col_Width": {}, + "Fine-Design_JSAPI_Public_Module_Table_Cell_Value": {}, + "Fine-Design_JSAPI_Public_Module_Table_Cell_Radius": {} + }, + "Fine-Design_JSAPI_Public_Module_Toolbar": { + "Fine-Design_JSAPI_Public_Module_Toolbar_Email_Button": {} + }, + "Fine-Design_JSAPI_Public_Module_Report_Page": { + "Fine-Design_JSAPI_Public_Module_Report_Page_Jump": {}, + "Fine-Design_JSAPI_Public_Module_Report_Page_Number_Get": {} + }, + "Fine-Design_JSAPI_Public_Module_Report_Export": {} + }, + "Fine-Design_JSAPI_Cpt": { + "Fine-Design_JSAPI_Cpt_Page_Preview": { + "Fine-Design_JSAPI_Cpt_Page_Preview_Folding_Tree": {} + }, + "Fine-Design_JSAPI_Cpt_Write_Preview": {}, + "Fine-Design_JSAPI_Cpt_View_Preview": { + "Fine-Design_JSAPI_Cpt_View_Preview_Report_Location": {} + } + }, + "Fine-Design_JSAPI_Form": { + "Fine-Design_JSAPI_Form_Component_Get": {}, + "Fine-Design_JSAPI_Form_Component_Universal": {}, + "Fine-Design_JSAPI_Form_Component_Tab": {} + } +} \ No newline at end of file diff --git a/designer-base/src/main/resources/com/fr/design/javascript/jsapi/jsapi.json b/designer-base/src/main/resources/com/fr/design/javascript/jsapi/jsapi.json new file mode 100644 index 000000000..e268f70e1 --- /dev/null +++ b/designer-base/src/main/resources/com/fr/design/javascript/jsapi/jsapi.json @@ -0,0 +1,33 @@ +{ + "Fine-Design_JSAPI_Public_Module_Global_Universal": ["_g()", "getParameterContainer", "parameterCommit", "loadContentPane", "getPreviewType"], + "Fine-Design_JSAPI_Public_Module_Global_FR": [ "servletURL", "serverURL", "server", "fineServletURL", "SessionMgr.getSessionID", "showDialog", "closeDialog", + "doHyperlinkByGet", "doHyperlinkByPost", "doURLPrint", "Msg", "remoteEvaluate", "jsonEncode", "jsonDecode", + "ajax", "isEmpty", "isArray", "cellStr2ColumnRow", "columnRow2CellStr"], + "Fine-Design_JSAPI_Public_Module_Global_FS": ["signOut", "tabPane.closeActiveTab", "tabPane.addItem"], + "Fine-Design_JSAPI_Public_Module_Global_Mobile": ["location", "Mobile.getDeviceInfo"], + "Fine-Design_JSAPI_Public_Module_Widget_Get": ["this", "this.options.form", "getWidgetByName"], + "Fine-Design_JSAPI_Public_Module_Widget_Universal": ["getValue", "getText", "setValue", "visible", "invisible", "setVisible", "isVisible", "setEnable", "isEnabled", + "reset", "getType", "setWaterMark", "fireEvent", "setPopupStyle"], + "Fine-Design_JSAPI_Public_Module_Date_Widget_Peculiar":["setMaxAndMinDate"], + "Fine-Design_JSAPI_Public_Module_Button_Widget_Peculiar":["doClick"], + "Fine-Design_JSAPI_Public_Module_Combobox_Widget_Peculiar":["setName4Empty"], + "Fine-Design_JSAPI_Public_Module_Table_Marquee":["startMarquee", "stopMarquee"], + "Fine-Design_JSAPI_Public_Module_Table_Scrollbar":["setHScrollBarVisible", "setVScrollBarVisible"], + "Fine-Design_JSAPI_Public_Module_Table_Cell_Style":["addEffect"], + "Fine-Design_JSAPI_Public_Module_Table_Row_Height_Col_Width":["setRowHeight", "setColWidth"], + "Fine-Design_JSAPI_Public_Module_Table_Cell_Value":["getCellValue", "setCellValue"], + "Fine-Design_JSAPI_Public_Module_Table_Cell_Radius":["setCellRadius"], + "Fine-Design_JSAPI_Public_Module_Toolbar":["toolBarFloat", "setStyle","getToolbar"], + "Fine-Design_JSAPI_Public_Module_Toolbar_Email_Button":["changeFormat"], + "Fine-Design_JSAPI_Public_Module_Report_Page_Jump":["gotoPreviousPage", "gotoNextPage", "gotoLastPage", "gotoFirstPage", "gotoPage"], + "Fine-Design_JSAPI_Public_Module_Report_Page_Number_Get":["getCurrentPageIndex", "getReportTotalPage", "currentPageIndex", "reportTotalPage"], + "Fine-Design_JSAPI_Public_Module_Report_Export":["exportReportToExcel", "exportReportToImage", "exportReportToPDF", "exportReportToWord"], + "Fine-Design_JSAPI_Cpt_Page_Preview_Folding_Tree":["expandNodeLayer", "collapseNodeLayer", "expandAllNodeLayer", "collapseAllNodeLayer"], + "Fine-Design_JSAPI_Cpt_Write_Preview":["getWidgetByCell", "appendReportRC", "appendReportRow", + "deleteReportRC", "deleteRows", "refreshAllSheets", "loadSheetByIndex", "loadSheetByName", "isDirtyPage", + "isAutoStash", "writeReport", "verifyAndWriteReport", "verifyReport", "importExcel", "importExcel_Clean", + "importExcel_Append", "importExcel_Cover", "stash", "clear"], + "Fine-Design_JSAPI_Cpt_View_Preview_Report_Location":["centerReport"], + "Fine-Design_JSAPI_Form_Component_Get":["getAllWidgets"], + "Fine-Design_JSAPI_Form_Component_Tab":["showCardByIndex", "showCardByIndex", "getShowIndex", "setTitleVisible"] +} \ No newline at end of file diff --git a/designer-base/src/test/java/com/fr/design/javascript/jsapi/JSAPITreeHelperTest.java b/designer-base/src/test/java/com/fr/design/javascript/jsapi/JSAPITreeHelperTest.java new file mode 100644 index 000000000..e0d5ae7c9 --- /dev/null +++ b/designer-base/src/test/java/com/fr/design/javascript/jsapi/JSAPITreeHelperTest.java @@ -0,0 +1,31 @@ +package com.fr.design.javascript.jsapi; + +import java.util.List; +import javax.swing.tree.DefaultMutableTreeNode; +import junit.framework.TestCase; + +public class JSAPITreeHelperTest extends TestCase { + public void testGetName(){ + List names = JSAPITreeHelper.getNames("Fine-Design_JSAPI_Public_Module_Toolbar"); + assertEquals(names.size(),4); + assertTrue(names.contains( "toolBarFloat")); + assertTrue(names.contains( "setStyle")); + assertTrue(names.contains( "getToolbar")); + assertTrue(names.contains( "changeFormat")); + List allNames = JSAPITreeHelper.getAllNames(); + assertEquals(allNames.size(),16); + } + + public void testGetDirectCategory(){ + String directCategory = JSAPITreeHelper.getDirectCategory("_g()"); + assertEquals(directCategory,"Fine-Design_JSAPI_Public_Module_Global_Universal"); + directCategory = JSAPITreeHelper.getDirectCategory("showCardByIndex"); + assertEquals(directCategory,"Fine-Design_JSAPI_Form_Component_Tab"); + } + + public void testCreateJSAPITree(){ + DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode(); + JSAPITreeHelper.createJSAPITree(rootNode); + assertEquals(2,rootNode.getChildCount()); + } +} diff --git a/designer-base/src/test/resources/com/fr/design/javascript/jsapi/category.json b/designer-base/src/test/resources/com/fr/design/javascript/jsapi/category.json new file mode 100644 index 000000000..2d0e50ba0 --- /dev/null +++ b/designer-base/src/test/resources/com/fr/design/javascript/jsapi/category.json @@ -0,0 +1,17 @@ +{ + "Fine-Design_JSAPI_Public_Module": { + "Fine-Design_JSAPI_Public_Module_Global": { + "Fine-Design_JSAPI_Public_Module_Global_Universal": {}, + "Fine-Design_JSAPI_Public_Module_Global_Mobile": {} + }, + "Fine-Design_JSAPI_Public_Module_Widget": { + "Fine-Design_JSAPI_Public_Module_Date_Widget_Peculiar": {} + }, + "Fine-Design_JSAPI_Public_Module_Toolbar": { + "Fine-Design_JSAPI_Public_Module_Toolbar_Email_Button": {} + } + }, + "Fine-Design_JSAPI_Form": { + "Fine-Design_JSAPI_Form_Component_Tab": {} + } +} \ No newline at end of file diff --git a/designer-base/src/test/resources/com/fr/design/javascript/jsapi/jsapi.json b/designer-base/src/test/resources/com/fr/design/javascript/jsapi/jsapi.json new file mode 100644 index 000000000..4ff0a321e --- /dev/null +++ b/designer-base/src/test/resources/com/fr/design/javascript/jsapi/jsapi.json @@ -0,0 +1,8 @@ +{ + "Fine-Design_JSAPI_Public_Module_Global_Universal": ["_g()", "getParameterContainer", "parameterCommit", "loadContentPane", "getPreviewType"], + "Fine-Design_JSAPI_Public_Module_Global_Mobile": ["location", "Mobile.getDeviceInfo"], + "Fine-Design_JSAPI_Public_Module_Date_Widget_Peculiar":["setMaxAndMinDate"], + "Fine-Design_JSAPI_Public_Module_Toolbar":["toolBarFloat", "setStyle","getToolbar"], + "Fine-Design_JSAPI_Public_Module_Toolbar_Email_Button":["changeFormat"], + "Fine-Design_JSAPI_Form_Component_Tab":["showCardByIndex", "showCardByIndex", "getShowIndex", "setTitleVisible"] +} \ No newline at end of file diff --git a/designer-realize/src/main/java/com/fr/design/javascript/ListenerEditPane.java b/designer-realize/src/main/java/com/fr/design/javascript/ListenerEditPane.java index 095d011f7..056707896 100644 --- a/designer-realize/src/main/java/com/fr/design/javascript/ListenerEditPane.java +++ b/designer-realize/src/main/java/com/fr/design/javascript/ListenerEditPane.java @@ -89,7 +89,7 @@ public class ListenerEditPane extends BasicBeanPane { card = new CardLayout(); hyperlinkPane = FRGUIPaneFactory.createCardLayout_S_Pane(); hyperlinkPane.setLayout(card); - JavaScriptImplPane javaScriptPane = new JavaScriptImplPane(defaultArgs); + JavaScriptImplPane javaScriptPane = new JavaScriptImplPane(defaultArgs,true); hyperlinkPane.add(JS, javaScriptPane); // 提交入库 List dbManiList = new ArrayList();